docshell/base/nsDocShell.cpp
author Jean-Yves Avenard <jyavenard@mozilla.com>
Thu, 28 May 2020 04:07:15 +0000
changeset 596498 9da04f54abf93bd87f3017f91ef5d2bdb16757c3
parent 596496 d08bb712835684001850881bbe70cdf6130f610e
child 597160 28b5472e0d8229a92a3ccb3fd14a470d672c4f01
child 597399 b7f14c4cfe5d68ae5518905e76d5ef9916a65999
permissions -rw-r--r--
Bug 1637869 - P4. Access pref via staticpref. r=mattwoodrow,necko-reviewers It would otherwise triggers browser/base/content/test/performance/browser_preferences_usage.js That pref would have been checked with every single load, a staticpref is O-1 . Differential Revision: https://phabricator.services.mozilla.com/D76317

/* -*- 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>

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

#include "mozilla/ArrayUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/Casting.h"
#include "mozilla/Components.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Encoding.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/HTMLEditor.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/Logging.h"
#include "mozilla/MediaFeatureChange.h"
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/ScrollTypes.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_extensions.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/StaticPrefs_ui.h"
#include "mozilla/StaticPrefs_fission.h"
#include "mozilla/StartupTimeline.h"
#include "mozilla/StorageAccess.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Unused.h"
#include "mozilla/WidgetUtils.h"

#include "mozilla/dom/ChildProcessChannelListener.h"
#include "mozilla/dom/ClientChannelHelper.h"
#include "mozilla/dom/ClientHandle.h"
#include "mozilla/dom/ClientInfo.h"
#include "mozilla/dom/ClientManager.h"
#include "mozilla/dom/ClientSource.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentFrameMessageManager.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLAnchorElement.h"
#include "mozilla/dom/PerformanceNavigation.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/PopupBlocker.h"
#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
#include "mozilla/dom/ScreenOrientation.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/ServiceWorkerInterceptController.h"
#include "mozilla/dom/ServiceWorkerUtils.h"
#include "mozilla/dom/SessionHistoryEntry.h"
#include "mozilla/dom/SessionStorageManager.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/UserActivation.h"
#include "mozilla/dom/ChildSHistory.h"
#include "mozilla/dom/nsCSPContext.h"
#include "mozilla/dom/nsHTTPSOnlyUtils.h"
#include "mozilla/dom/LoadURIOptionsBinding.h"
#include "mozilla/dom/JSWindowActorChild.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/net/DocumentChannel.h"
#include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "ReferrerInfo.h"

#include "nsIApplicationCacheChannel.h"
#include "nsIApplicationCacheContainer.h"
#include "nsIAppShell.h"
#include "nsIAuthPrompt.h"
#include "nsIAuthPrompt2.h"
#include "nsICachingChannel.h"
#include "nsICaptivePortalService.h"
#include "nsIChannel.h"
#include "nsIChannelEventSink.h"
#include "nsIClassOfService.h"
#include "nsIConsoleReportCollector.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "nsIContentSecurityPolicy.h"
#include "nsIContentViewer.h"
#include "nsIController.h"
#include "nsIDocShellTreeItem.h"
#include "nsIDocShellTreeOwner.h"
#include "mozilla/dom/Document.h"
#include "nsIDocumentLoaderFactory.h"
#include "nsIDOMWindow.h"
#include "nsIEditingSession.h"
#include "nsIEffectiveTLDService.h"
#include "nsIExternalProtocolService.h"
#include "nsIFormPOSTActionChannel.h"
#include "nsIFrame.h"
#include "nsIGlobalObject.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIIDNService.h"
#include "nsIInputStreamChannel.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsILayoutHistoryState.h"
#include "nsILoadInfo.h"
#include "nsIMultiPartChannel.h"
#include "nsINestedURI.h"
#include "nsINetworkPredictor.h"
#include "nsINode.h"
#include "nsINSSErrorsService.h"
#include "nsIObserverService.h"
#include "nsIOService.h"
#include "nsIPrincipal.h"
#include "nsIPrivacyTransitionObserver.h"
#include "nsIPrompt.h"
#include "nsIPromptFactory.h"
#include "nsIReflowObserver.h"
#include "nsIScriptChannel.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIScriptSecurityManager.h"
#include "nsIScrollableFrame.h"
#include "nsIScrollObserver.h"
#include "nsISecureBrowserUI.h"
#include "nsISeekableStream.h"
#include "nsISelectionDisplay.h"
#include "nsISHEntry.h"
#include "nsISiteSecurityService.h"
#include "nsISocketProvider.h"
#include "nsIStringBundle.h"
#include "nsIStructuredCloneContainer.h"
#include "nsIBrowserChild.h"
#include "nsITextToSubURI.h"
#include "nsITimedChannel.h"
#include "nsITimer.h"
#include "nsITransportSecurityInfo.h"
#include "nsIUploadChannel.h"
#include "nsIURIFixup.h"
#include "nsIURIMutator.h"
#include "nsIURILoader.h"
#include "nsIViewSourceChannel.h"
#include "nsIWebBrowserChrome.h"
#include "nsIWebBrowserChrome3.h"
#include "nsIWebBrowserChromeFocus.h"
#include "nsIWebBrowserFind.h"
#include "nsIWebProgress.h"
#include "nsIWidget.h"
#include "nsIWindowWatcher.h"
#include "nsIWritablePropertyBag2.h"

#include "nsCommandManager.h"
#include "nsPIDOMWindow.h"
#include "nsPIWindowRoot.h"

#include "IHistory.h"
#include "IUrlClassifierUITelemetry.h"

#include "nsArray.h"
#include "nsArrayUtils.h"
#include "nsCExternalHandlerService.h"
#include "nsContentDLF.h"
#include "nsContentPolicyUtils.h"  // NS_CheckContentLoadPolicy(...)
#include "nsContentSecurityManager.h"
#include "nsContentSecurityUtils.h"
#include "nsContentUtils.h"
#include "nsCURILoader.h"
#include "nsDocShellCID.h"
#include "nsDocShellEditorData.h"
#include "nsDocShellEnumerator.h"
#include "nsDocShellLoadState.h"
#include "nsDocShellLoadTypes.h"
#include "nsDOMCID.h"
#include "nsDOMNavigationTiming.h"
#include "nsDSURIContentListener.h"
#include "nsEditingSession.h"
#include "nsError.h"
#include "nsEscape.h"
#include "nsFocusManager.h"
#include "nsGlobalWindow.h"
#include "nsISearchService.h"
#include "nsJSEnvironment.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsObjectLoadingContent.h"
#include "nsPingListener.h"
#include "nsPoint.h"
#include "nsQueryObject.h"
#include "nsQueryActor.h"
#include "nsRect.h"
#include "nsRefreshTimer.h"
#include "nsSandboxFlags.h"
#include "nsSHEntry.h"
#include "nsSHistory.h"
#include "nsSHEntry.h"
#include "nsStructuredCloneContainer.h"
#include "nsSubDocumentFrame.h"
#include "nsView.h"
#include "nsViewManager.h"
#include "nsViewSourceHandler.h"
#include "nsWebBrowserFind.h"
#include "nsWhitespaceTokenizer.h"
#include "nsWidgetsCID.h"
#include "nsXULAppAPI.h"

#include "GeckoProfiler.h"
#include "mozilla/NullPrincipal.h"
#include "Navigator.h"
#include "prenv.h"
#include "URIUtils.h"

#include "timeline/JavascriptTimelineMarker.h"
#include "nsDocShellTelemetryUtils.h"

#ifdef MOZ_PLACES
#  include "nsIFaviconService.h"
#  include "mozIPlacesPendingOperation.h"
#endif

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

#ifdef MOZ_GECKO_PROFILER
#  include "ProfilerMarkerPayload.h"
#endif

using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::net;

using mozilla::ipc::Endpoint;

// Threshold value in ms for META refresh based redirects
#define REFRESH_REDIRECT_TIMER 15000

// 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

static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);

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

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

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

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

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 bool IsTopLevelDoc(BrowsingContext* aBrowsingContext,
                          nsILoadInfo* aLoadInfo) {
  MOZ_ASSERT(aBrowsingContext);
  MOZ_ASSERT(aLoadInfo);

  if (aLoadInfo->GetExternalContentPolicyType() !=
      nsIContentPolicy::TYPE_DOCUMENT) {
    return false;
  }

  return aBrowsingContext->IsTopContent();
}

// True if loading for top level document loading in active tab.
static bool IsUrgentStart(BrowsingContext* aBrowsingContext,
                          nsILoadInfo* aLoadInfo, uint32_t aLoadType) {
  MOZ_ASSERT(aBrowsingContext);
  MOZ_ASSERT(aLoadInfo);

  if (!IsTopLevelDoc(aBrowsingContext, aLoadInfo)) {
    return false;
  }

  if (aLoadType &
      (nsIDocShell::LOAD_CMD_NORMAL | nsIDocShell::LOAD_CMD_HISTORY)) {
    return true;
  }

  return aBrowsingContext->GetIsActive();
}

nsDocShell::nsDocShell(BrowsingContext* aBrowsingContext,
                       uint64_t aContentWindowID)
    : nsDocLoader(),
      mHistoryID(aBrowsingContext->GetHistoryID()),
      mContentWindowID(aContentWindowID),
      mBrowsingContext(aBrowsingContext),
      mForcedCharset(nullptr),
      mParentCharset(nullptr),
      mTreeOwner(nullptr),
      mScrollbarPref(ScrollbarPreference::Auto),
      mCharsetReloadState(eCharsetReloadInit),
      mParentCharsetSource(0),
      mFrameMargins(-1, -1),
      mItemType(aBrowsingContext->IsContent() ? typeContent : typeChrome),
      mPreviousEntryIndex(-1),
      mLoadedEntryIndex(-1),
      mChildOffset(0),
      mBusyFlags(BUSY_FLAGS_NONE),
      mAppType(nsIDocShell::APP_TYPE_UNKNOWN),
      mLoadType(0),
      mFailedLoadType(0),
      mDisplayMode(nsIDocShell::DISPLAY_MODE_BROWSER),
      mJSRunToCompletionDepth(0),
      mTouchEventsOverride(nsIDocShell::TOUCHEVENTS_OVERRIDE_NONE),
      mMetaViewportOverride(nsIDocShell::META_VIEWPORT_OVERRIDE_NONE),
      mFullscreenAllowed(CHECK_ATTRIBUTES),
      mCreatingDocument(false),
#ifdef DEBUG
      mInEnsureScriptEnv(false),
#endif
      mCreated(false),
      mAllowSubframes(true),
      mAllowJavascript(true),
      mAllowMetaRedirects(true),
      mAllowImages(true),
      mAllowMedia(true),
      mAllowDNSPrefetch(true),
      mAllowWindowControl(true),
      mUseErrorPages(true),
      mCSSErrorReportingEnabled(false),
      mAllowAuth(mItemType == typeContent),
      mAllowKeywordFixup(false),
      mIsOffScreenBrowser(false),
      mDisableMetaRefreshWhenInactive(false),
      mIsAppTab(false),
      mDeviceSizeIsPageSize(false),
      mWindowDraggingAllowed(false),
      mInFrameSwap(false),
      mCanExecuteScripts(false),
      mFiredUnloadEvent(false),
      mEODForCurrentDocument(false),
      mURIResultedInDocument(false),
      mIsBeingDestroyed(false),
      mIsExecutingOnLoadHandler(false),
      mIsPrintingOrPP(false),
      mSavingOldViewer(false),
      mDynamicallyCreated(false),
      mAffectPrivateSessionLifetime(true),
      mInvisible(false),
      mHasLoadedNonBlankURI(false),
      mBlankTiming(false),
      mTitleValidForCurrentURI(false),
      mWillChangeProcess(false),
      mIsNavigating(false),
      mSuspendMediaWhenInactive(false) {
  // If no outer window ID was provided, generate a new one.
  if (aContentWindowID == 0) {
    mContentWindowID = nsContentUtils::GenerateWindowId();
  }

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

#ifdef DEBUG
  // We're counting the number of |nsDocShells| to help find leaks
  ++gNumberOfDocShells;
  MOZ_LOG(gDocShellAndDOMWindowLeakLogging, LogLevel::Info,
          ("++DOCSHELL %p == %ld [pid = %d] [id = %s]\n", (void*)this,
           gNumberOfDocShells, getpid(), nsIDToCString(mHistoryID).get()));
#endif
}

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

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

  Destroy();

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

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

#ifdef DEBUG
  if (MOZ_LOG_TEST(gDocShellAndDOMWindowLeakLogging, LogLevel::Info)) {
    nsAutoCString url;
    if (mLastOpenedURI) {
      url = mLastOpenedURI->GetSpecOrDefault();

      // Data URLs can be very long, so truncate to avoid flooding the log.
      const uint32_t maxURLLength = 1000;
      if (url.Length() > maxURLLength) {
        url.Truncate(maxURLLength);
      }
    }

    // We're counting the number of |nsDocShells| to help find leaks
    --gNumberOfDocShells;
    MOZ_LOG(gDocShellAndDOMWindowLeakLogging, LogLevel::Info,
            ("--DOCSHELL %p == %ld [pid = %d] [id = %s] [url = %s]\n",
             (void*)this, gNumberOfDocShells, getpid(),
             nsIDToCString(mHistoryID).get(), url.get()));
  }
#endif
}

/* static */
already_AddRefed<nsDocShell> nsDocShell::Create(
    BrowsingContext* aBrowsingContext, uint64_t aContentWindowID) {
  MOZ_ASSERT(aBrowsingContext, "DocShell without a BrowsingContext!");

  nsresult rv;
  RefPtr<nsDocShell> ds = new nsDocShell(aBrowsingContext, aContentWindowID);

  // Initialize the underlying nsDocLoader.
  rv = ds->nsDocLoader::InitWithBrowsingContext(aBrowsingContext);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return nullptr;
  }

  // Create our ContentListener
  ds->mContentListener = new nsDSURIContentListener(ds);

  // If parent intercept is not enabled then we must forward to
  // the network controller from docshell.  We also enable if we're
  // in the parent process in order to support non-e10s configurations.
  // Note: This check is duplicated in SharedWorkerInterfaceRequestor's
  // constructor.
  if (!ServiceWorkerParentInterceptEnabled() || XRE_IsParentProcess()) {
    ds->mInterceptController = new ServiceWorkerInterceptController();
  }

  // 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(ds);
  ds->mLoadGroup->SetNotificationCallbacks(proxy);

  // XXX(nika): We have our BrowsingContext, so we might be able to skip this.
  // It could be nice to directly set up our DocLoader tree?
  rv = nsDocLoader::AddDocLoaderAsChildOfRoot(ds);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return nullptr;
  }

  // Add |ds| as 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 |ds|, so it's ok.
  rv = ds->AddProgressListener(ds, nsIWebProgress::NOTIFY_STATE_DOCUMENT |
                                       nsIWebProgress::NOTIFY_STATE_NETWORK |
                                       nsIWebProgress::NOTIFY_LOCATION);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return nullptr;
  }

  // If our BrowsingContext has private browsing enabled, update the number of
  // private browsing docshells.
  if (aBrowsingContext->UsePrivateBrowsing()) {
    ds->NotifyPrivateBrowsingChanged();
  }

  // If our parent is present in this process, set up our parent now.
  RefPtr<BrowsingContext> parent = aBrowsingContext->GetParent();
  if (parent && parent->GetDocShell()) {
    parent->GetDocShell()->AddChild(ds);
  }

  // Make |ds| the primary DocShell for the given context.
  aBrowsingContext->SetDocShell(ds);

  // Set |ds| default load flags on load group.
  ds->SetLoadGroupDefaultLoadFlags(aBrowsingContext->GetDefaultLoadFlags());

  return ds.forget();
}

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_CYCLE_COLLECTION_WEAK_PTR_INHERITED(nsDocShell, nsDocLoader,
                                            mScriptGlobal, mInitialClientSource,
                                            mBrowsingContext,
                                            mChromeEventHandler)

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

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(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(nsIRefreshURI)
  NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
  NS_INTERFACE_MAP_ENTRY(nsIWebPageDescriptor)
  NS_INTERFACE_MAP_ENTRY(nsIAuthPromptProvider)
  NS_INTERFACE_MAP_ENTRY(nsILoadContext)
  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsINetworkInterceptController,
                                     mInterceptController)
  NS_INTERFACE_MAP_ENTRY(nsIDeprecationWarner)
NS_INTERFACE_MAP_END_INHERITING(nsDocLoader)

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

  *aSink = nullptr;

  if (aIID.Equals(NS_GET_IID(nsICommandManager))) {
    NS_ENSURE_SUCCESS(EnsureCommandHandler(), NS_ERROR_FAILURE);
    *aSink = static_cast<nsICommandManager*>(mCommandManager.get());
  } 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))) &&
             NS_SUCCEEDED(EnsureScriptEnvironment())) {
    return mScriptGlobal->QueryInterface(aIID, aSink);
  } else if (aIID.Equals(NS_GET_IID(Document)) &&
             NS_SUCCEEDED(EnsureContentViewer())) {
    RefPtr<Document> 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;
    }

    RefPtr<Document> doc = contentViewer->GetDocument();
    NS_ASSERTION(doc, "Should have a document.");
    if (!doc) {
      return NS_ERROR_NO_INTERFACE;
    }

#if defined(DEBUG)
    MOZ_LOG(
        gDocShellLog, LogLevel::Debug,
        ("nsDocShell[%p]: returning app cache container %p", this, doc.get()));
#endif
    return doc->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, &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))) {
    RefPtr<ChildSHistory> shistory = GetSessionHistory();
    if (shistory) {
      // XXX(nika): Stop exposing nsISHistory through GetInterface.
      nsCOMPtr<nsISHistory> legacy = shistory->LegacySHistory();
      legacy.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(nsISelectionDisplay))) {
    if (PresShell* presShell = GetPresShell()) {
      return presShell->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(nsIBrowserChild))) {
    *aSink = GetBrowserChild().take();
    return *aSink ? NS_OK : NS_ERROR_FAILURE;
  } else {
    return nsDocLoader::GetInterface(aIID, aSink);
  }

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

NS_IMETHODIMP
nsDocShell::SetCancelContentJSEpoch(int32_t aEpoch) {
  // Note: this gets called fairly early (before a pageload actually starts).
  // We could probably defer this even longer.
  nsCOMPtr<nsIBrowserChild> browserChild = GetBrowserChild();
  static_cast<BrowserChild*>(browserChild.get())
      ->SetCancelContentJSEpoch(aEpoch);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::LoadURI(nsDocShellLoadState* aLoadState, bool aSetNavigating) {
  MOZ_ASSERT(aLoadState, "Must have a valid load state!");
  MOZ_ASSERT(
      (aLoadState->LoadFlags() & INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 0,
      "Should not have these flags set");

  if (!aLoadState->TriggeringPrincipal()) {
    MOZ_ASSERT(false, "LoadURI must have a triggering principal");
    return NS_ERROR_FAILURE;
  }

  bool oldIsNavigating = mIsNavigating;
  auto cleanupIsNavigating =
      MakeScopeExit([&]() { mIsNavigating = oldIsNavigating; });
  if (aSetNavigating) {
    mIsNavigating = true;
  }

  PopupBlocker::PopupControlState popupState;
  if (aLoadState->LoadFlags() & LOAD_FLAGS_ALLOW_POPUPS) {
    popupState = PopupBlocker::openAllowed;
  } else {
    popupState = PopupBlocker::openOverridden;
  }
  AutoPopupStatePusher statePusher(popupState);

  if (aLoadState->GetOriginalURIString().isSome()) {
    // Save URI string in case it's needed later when
    // sending to search engine service in EndPageLoad()
    mOriginalUriString = *aLoadState->GetOriginalURIString();
  }

  if (aLoadState->GetCancelContentJSEpoch().isSome()) {
    SetCancelContentJSEpoch(*aLoadState->GetCancelContentJSEpoch());
  }

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

  nsLoadFlags defaultLoadFlags = mBrowsingContext->GetDefaultLoadFlags();
  if (aLoadState->LoadFlags() & LOAD_FLAGS_FORCE_TRR) {
    defaultLoadFlags |= nsIRequest::LOAD_TRR_ONLY_MODE;
  } else if (aLoadState->LoadFlags() & LOAD_FLAGS_DISABLE_TRR) {
    defaultLoadFlags |= nsIRequest::LOAD_TRR_DISABLED_MODE;
  }
  mBrowsingContext->SetDefaultLoadFlags(defaultLoadFlags);

  if (!StartupTimeline::HasRecord(StartupTimeline::FIRST_LOAD_URI) &&
      mItemType == typeContent && !NS_IsAboutBlank(aLoadState->URI())) {
    StartupTimeline::RecordOnce(StartupTimeline::FIRST_LOAD_URI);
  }

  // LoadType used to be set to a default value here, if no LoadInfo/LoadState
  // object was passed in. That functionality has been removed as of bug
  // 1492648. LoadType should now be set up by the caller at the time they
  // create their nsDocShellLoadState object to pass into LoadURI.

  MOZ_LOG(
      gDocShellLeakLog, LogLevel::Debug,
      ("nsDocShell[%p]: loading %s with flags 0x%08x", this,
       aLoadState->URI()->GetSpecOrDefault().get(), aLoadState->LoadFlags()));

  if (!aLoadState->SHEntry() &&
      !LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(),
                           LOAD_FLAGS_REPLACE_HISTORY)) {
    // This is possibly a subframe, so handle it accordingly.
    //
    // If history exists, it will be loaded into the aLoadState object, and the
    // LoadType will be changed.
    MaybeHandleSubframeHistory(aLoadState);
  }

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

    return LoadHistoryEntry(aLoadState->SHEntry(), aLoadState->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 ((aLoadState->LoadType() == LOAD_NORMAL ||
       aLoadState->LoadType() == LOAD_STOP_CONTENT) &&
      ShouldBlockLoadingForBackButton()) {
    return NS_OK;
  }

  BrowsingContext::Type bcType = mBrowsingContext->GetType();

  // Set up the inheriting principal in LoadState.
  nsresult rv = aLoadState->SetupInheritingPrincipal(
      bcType, mBrowsingContext->OriginAttributesRef());
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aLoadState->SetupTriggeringPrincipal(
      mBrowsingContext->OriginAttributesRef());
  NS_ENSURE_SUCCESS(rv, rv);

  aLoadState->CalculateLoadURIFlags();

  MOZ_ASSERT(aLoadState->TypeHint().IsVoid(),
             "Typehint should be null when calling InternalLoad from LoadURI");
  MOZ_ASSERT(aLoadState->FileName().IsVoid(),
             "FileName should be null when calling InternalLoad from LoadURI");
  MOZ_ASSERT(aLoadState->SHEntry() == nullptr,
             "SHEntry should be null when calling InternalLoad from LoadURI");

  return InternalLoad(aLoadState,
                      nullptr,   // no nsIDocShell
                      nullptr);  // no nsIRequest
}

void nsDocShell::MaybeHandleSubframeHistory(nsDocShellLoadState* aLoadState) {
  // First, verify if this is a subframe.
  nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
  GetInProcessSameTypeParent(getter_AddRefs(parentAsItem));
  nsCOMPtr<nsIDocShell> parentDS(do_QueryInterface(parentAsItem));

  if (!parentDS || parentDS == static_cast<nsIDocShell*>(this)) {
    // 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) {
      aLoadState->SetLoadType(LOAD_NORMAL_REPLACE);
    }
    return;
  }

  /* OK. It is a subframe. Checkout the parent's loadtype. If the parent was
   * loaded through 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
  uint32_t parentLoadType;
  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) {
    // Only use the old SHEntry, if we're sure enough that
    // it wasn't originally for some other frame.
    nsCOMPtr<nsISHEntry> shEntry;
    currentSH->GetChildSHEntryIfHasNoDynamicallyAddedChild(
        mChildOffset, getter_AddRefs(shEntry));
    if (shEntry) {
      aLoadState->SetSHEntry(shEntry);
    }
  }

  // Make some decisions on the child frame's loadType based on the
  // parent's loadType, if the subframe hasn't loaded anything into it.
  //
  // In some cases privileged scripts may try to get the DOMWindow
  // reference of this docshell before the loading starts, causing the
  // initial about:blank content viewer being created and mCurrentURI being
  // set. To handle this case we check if mCurrentURI is about:blank and
  // currentSHEntry is null.
  nsCOMPtr<nsISHEntry> currentChildEntry;
  GetCurrentSHEntry(getter_AddRefs(currentChildEntry), &oshe);

  if (mCurrentURI && (!NS_IsAboutBlank(mCurrentURI) || currentChildEntry)) {
    // This is a pre-existing subframe. If
    // 1. The load of this frame was not originally initiated by session
    //    history directly (i.e. (!shEntry) condition succeeded, but it can
    //    still be a history load on parent which causes this frame being
    //    loaded), which we checked with the above assert, and
    // 2. mCurrentURI is not null, nor the initial about:blank,
    // 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.
    BusyFlags parentBusy = parentDS->GetBusyFlags();
    BusyFlags selfBusy = GetBusyFlags();

    if (parentBusy & BUSY_FLAGS_BUSY || selfBusy & BUSY_FLAGS_BUSY) {
      aLoadState->SetLoadType(LOAD_NORMAL_REPLACE);
      aLoadState->SetSHEntry(nullptr);
    }
    return;
  }

  // This is a newly created frame. Check for exception cases first.
  // By default the subframe will inherit the parent's loadType.
  if (aLoadState->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) {
      aLoadState->SetLoadType(LOAD_NORMAL_REPLACE);
      aLoadState->SetSHEntry(nullptr);
    }
  } else if (parentLoadType == LOAD_REFRESH) {
    // Clear shEntry. For refresh loads, we have to load
    // what comes through the pipe, not what's in history.
    aLoadState->SetSHEntry(nullptr);
  } else if ((parentLoadType == LOAD_BYPASS_HISTORY) ||
             (aLoadState->SHEntry() &&
              ((parentLoadType & LOAD_CMD_HISTORY) ||
               (parentLoadType == LOAD_RELOAD_NORMAL) ||
               (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE) ||
               (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE) ||
               (parentLoadType ==
                LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE)))) {
    // 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.
    aLoadState->SetLoadType(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.
    aLoadState->SetLoadType(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
    aLoadState->SetLoadType(parentLoadType);
  }
}

/*
 * 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() {
  // Clear out our form control state, because the state of controls
  // in the pre-open() document should not affect the state of
  // controls that are now going to be written.
  SetLayoutHistoryState(nullptr);
  mEODForCurrentDocument = false;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::FirePageHideNotification(bool aIsUnload) {
  FirePageHideNotificationInternal(aIsUnload, false);
  return NS_OK;
}

void nsDocShell::FirePageHideNotificationInternal(
    bool aIsUnload, bool aSkipCheckingDynEntries) {
  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) {
      RefPtr<nsDocShell> child = static_cast<nsDocShell*>(kids[i].get());
      if (child) {
        // Skip checking dynamic subframe entries in our children.
        child->FirePageHideNotificationInternal(aIsUnload, true);
      }
    }

    // If the document is unloading, remove all dynamic subframe entries.
    if (aIsUnload && !aSkipCheckingDynEntries) {
      RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
      if (rootSH && mOSHE) {
        int32_t index = rootSH->Index();
        rootSH->LegacySHistory()->RemoveDynEntries(index, mOSHE);
      }
    }

    // Now make sure our editor, if any, is detached before we go
    // any farther.
    DetachEditorFromWindow();
  }
}

nsresult nsDocShell::Dispatch(TaskCategory aCategory,
                              already_AddRefed<nsIRunnable>&& aRunnable) {
  nsCOMPtr<nsIRunnable> runnable(aRunnable);
  nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
  if (NS_WARN_IF(!win)) {
    // Window should only be unavailable after destroyed.
    MOZ_ASSERT(mIsBeingDestroyed);
    return NS_ERROR_FAILURE;
  }

  if (win->GetDocGroup()) {
    return win->GetDocGroup()->Dispatch(aCategory, runnable.forget());
  }

  return SchedulerGroup::Dispatch(aCategory, runnable.forget());
}

NS_IMETHODIMP
nsDocShell::DispatchLocationChangeEvent() {
  return Dispatch(
      TaskCategory::Other,
      NewRunnableMethod("nsDocShell::FireDummyOnLocationChange", this,
                        &nsDocShell::FireDummyOnLocationChange));
}

NS_IMETHODIMP
nsDocShell::StartDelayedAutoplayMediaComponents() {
  RefPtr<nsPIDOMWindowOuter> outerWindow = GetWindow();
  if (outerWindow) {
    outerWindow->SetMediaSuspend(nsISuspendedTypes::NONE_SUSPENDED);
  }
  return NS_OK;
}

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

  bool canBeReset = false;

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

  if (!mTiming) {
    mTiming = new nsDOMNavigationTiming(this);
    canBeReset = true;
  }

  mTiming->NotifyNavigationStart(
      mBrowsingContext->GetIsActive()
          ? nsDOMNavigationTiming::DocShellState::eActive
          : nsDOMNavigationTiming::DocShellState::eInactive);

  return canBeReset;
}

void nsDocShell::MaybeResetInitTiming(bool aReset) {
  if (aReset) {
    mTiming = nullptr;
  }
}

nsDOMNavigationTiming* nsDocShell::GetNavigationTiming() const {
  return mTiming;
}

//
// 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(BrowsingContext* aOrigin,
                                BrowsingContext* aTarget) {
  nsIDocShell* originDocShell = aOrigin->GetDocShell();
  MOZ_ASSERT(originDocShell, "originDocShell must not be null");
  Document* originDocument = originDocShell->GetDocument();
  NS_ENSURE_TRUE(originDocument, false);

  nsIDocShell* targetDocShell = aTarget->GetDocShell();
  MOZ_ASSERT(targetDocShell, "targetDocShell must not be null");
  Document* targetDocument = targetDocShell->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
  nsCOMPtr<nsIURI> originURI;
  nsCOMPtr<nsIURI> targetURI;
  nsCOMPtr<nsIURI> innerOriginURI;
  nsCOMPtr<nsIURI> innerTargetURI;

  // Casting to BasePrincipal, as we can't get InnerMost URI otherwise
  auto* originDocumentBasePrincipal =
      BasePrincipal::Cast(originDocument->NodePrincipal());

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

  auto* targetDocumentBasePrincipal =
      BasePrincipal::Cast(targetDocument->NodePrincipal());

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

  return innerOriginURI && innerTargetURI && SchemeIsFile(innerOriginURI) &&
         SchemeIsFile(innerTargetURI);
}

nsPresContext* nsDocShell::GetEldestPresContext() {
  nsIContentViewer* viewer = mContentViewer;
  while (viewer) {
    nsIContentViewer* prevViewer = viewer->GetPreviousViewer();
    if (!prevViewer) {
      return viewer->GetPresContext();
    }
    viewer = prevViewer;
  }

  return nullptr;
}

nsPresContext* nsDocShell::GetPresContext() {
  if (!mContentViewer) {
    return nullptr;
  }

  return mContentViewer->GetPresContext();
}

PresShell* nsDocShell::GetPresShell() {
  nsPresContext* presContext = GetPresContext();
  return presContext ? presContext->GetPresShell() : nullptr;
}

PresShell* nsDocShell::GetEldestPresShell() {
  nsPresContext* presContext = GetEldestPresContext();

  if (presContext) {
    return presContext->GetPresShell();
  }

  return nullptr;
}

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

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

NS_IMETHODIMP
nsDocShell::GetOuterWindowID(uint64_t* aWindowID) {
  *aWindowID = mContentWindowID;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetChromeEventHandler(EventTarget* aChromeEventHandler) {
  mChromeEventHandler = aChromeEventHandler;

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

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetChromeEventHandler(EventTarget** aChromeEventHandler) {
  NS_ENSURE_ARG_POINTER(aChromeEventHandler);
  RefPtr<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) {
  MOZ_ASSERT(!mIsBeingDestroyed);

  MOZ_LOG(gDocShellLeakLog, LogLevel::Debug,
          ("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;
  }

  bool uriIsEqual = false;
  if (!mCurrentURI || !aURI ||
      NS_FAILED(mCurrentURI->Equals(aURI, &uriIsEqual)) || !uriIsEqual) {
    mTitleValidForCurrentURI = false;
  }

  mCurrentURI = aURI;

#ifdef DEBUG
  mLastOpenedURI = aURI;
#endif

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

  bool isRoot = mBrowsingContext->IsTop();
  bool isSubFrame = false;  // Is this a subframe navigation?

  if (mLSHE) {
    isSubFrame = mLSHE->GetIsSubFrame();
  }

  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();

  PresShell* presShell = GetPresShell();
  NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
  Document* doc = presShell->GetDocument();
  NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
  doc->GetDocumentCharacterSet()->Name(aCharset);
  return NS_OK;
}

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

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

  Telemetry::ScalarSet(Telemetry::ScalarID::ENCODING_OVERRIDE_USED, true);

  nsIURI* url = doc->GetOriginalURI();
  bool isFileURL = url && SchemeIsFile(url);

  int32_t charsetSource = doc->GetDocumentCharacterSetSource();
  switch (charsetSource) {
    case kCharsetFromTopLevelDomain:
      // Unlabeled doc on a domain that we map to a fallback encoding
      Telemetry::AccumulateCategorical(
          Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION::RemoteTld);
      break;
    case kCharsetFromFallback:
    case kCharsetFromDocTypeDefault:
    case kCharsetFromCache:
    case kCharsetFromParentFrame:
      // Changing charset on an unlabeled doc.
      if (isFileURL) {
        Telemetry::AccumulateCategorical(
            Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION::Local);
      } else {
        Telemetry::AccumulateCategorical(
            Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION::RemoteNonTld);
      }
      break;
    case kCharsetFromInitialAutoDetection:
    case kCharsetFromFinalAutoDetection:
      // Changing charset on unlabeled doc where chardet fired
      if (isFileURL) {
        Telemetry::AccumulateCategorical(
            Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION::LocalChardet);
      } else {
        Telemetry::AccumulateCategorical(
            Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION::RemoteChardet);
      }
      break;
    case kCharsetFromMetaPrescan:
    case kCharsetFromMetaTag:
    case kCharsetFromChannel:
      // Changing charset on a doc that had a charset label.
      Telemetry::AccumulateCategorical(
          Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION::Labeled);
      break;
    case kCharsetFromParentForced:
    case kCharsetFromUserForced:
      // Changing charset on a document that already had an override.
      Telemetry::AccumulateCategorical(
          Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION::AlreadyOverridden);
      break;
    case kCharsetFromIrreversibleAutoDetection:
    case kCharsetFromOtherComponent:
    case kCharsetFromByteOrderMark:
    case kCharsetUninitialized:
    default:
      // Bug. This isn't supposed to happen.
      Telemetry::AccumulateCategorical(
          Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION::Bug);
      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 = nullptr;
    return NS_OK;
  }
  const Encoding* encoding = Encoding::ForLabel(aCharset);
  if (!encoding) {
    // Reject unknown labels
    return NS_ERROR_INVALID_ARG;
  }
  if (!encoding->IsAsciiCompatible() && encoding != ISO_2022_JP_ENCODING) {
    // Reject XSS hazards
    return NS_ERROR_INVALID_ARG;
  }
  mForcedCharset = encoding;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetForcedCharset(nsACString& aResult) {
  if (mForcedCharset) {
    mForcedCharset->Name(aResult);
  } else {
    aResult.Truncate();
  }
  return NS_OK;
}

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

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

NS_IMETHODIMP
nsDocShell::GetHasTrackingContentBlocked(Promise** aPromise) {
  MOZ_ASSERT(aPromise);

  ErrorResult rv;
  RefPtr<Document> doc(GetDocument());
  RefPtr<Promise> retPromise = Promise::Create(doc->GetOwnerGlobal(), rv);
  if (NS_WARN_IF(rv.Failed())) {
    return rv.StealNSResult();
  }

  // Retrieve the document's content blocking events from the parent process.
  RefPtr<Document::GetContentBlockingEventsPromise> promise =
      doc->GetContentBlockingEvents();
  if (promise) {
    promise->Then(
        GetCurrentThreadSerialEventTarget(), __func__,
        [retPromise](const Document::GetContentBlockingEventsPromise::
                         ResolveOrRejectValue& aValue) {
          if (aValue.IsResolve()) {
            bool has = aValue.ResolveValue() &
                       nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT;
            retPromise->MaybeResolve(has);
          } else {
            retPromise->MaybeResolve(false);
          }
        });
  } else {
    retPromise->MaybeResolve(false);
  }

  retPromise.forget(aPromise);
  return NS_OK;
}

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

  *aAllowPlugins = mBrowsingContext->GetAllowPlugins();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetAllowPlugins(bool aAllowPlugins) {
  mBrowsingContext->SetAllowPlugins(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::GetCssErrorReportingEnabled(bool* aEnabled) {
  MOZ_ASSERT(aEnabled);
  *aEnabled = mCSSErrorReportingEnabled;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetCssErrorReportingEnabled(bool aEnabled) {
  mCSSErrorReportingEnabled = aEnabled;
  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);
  return mBrowsingContext->GetUsePrivateBrowsing(aUsePrivateBrowsing);
}

void nsDocShell::NotifyPrivateBrowsingChanged() {
  MOZ_ASSERT(!mIsBeingDestroyed);

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

  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(UsePrivateBrowsing());
    }
  }
}

NS_IMETHODIMP
nsDocShell::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) {
  return mBrowsingContext->SetUsePrivateBrowsing(aUsePrivateBrowsing);
}

NS_IMETHODIMP
nsDocShell::SetPrivateBrowsing(bool aUsePrivateBrowsing) {
  return mBrowsingContext->SetPrivateBrowsing(aUsePrivateBrowsing);
}

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);
  return mBrowsingContext->GetUseRemoteTabs(aUseRemoteTabs);
}

NS_IMETHODIMP
nsDocShell::SetRemoteTabs(bool aUseRemoteTabs) {
  return mBrowsingContext->SetRemoteTabs(aUseRemoteTabs);
}

NS_IMETHODIMP
nsDocShell::GetUseRemoteSubframes(bool* aUseRemoteSubframes) {
  NS_ENSURE_ARG_POINTER(aUseRemoteSubframes);
  return mBrowsingContext->GetUseRemoteSubframes(aUseRemoteSubframes);
}

NS_IMETHODIMP
nsDocShell::SetRemoteSubframes(bool aUseRemoteSubframes) {
  return mBrowsingContext->SetRemoteSubframes(aUseRemoteSubframes);
}

NS_IMETHODIMP
nsDocShell::SetAffectPrivateSessionLifetime(bool aAffectLifetime) {
  MOZ_ASSERT(!mIsBeingDestroyed);

  bool change = aAffectLifetime != mAffectPrivateSessionLifetime;
  if (change && UsePrivateBrowsing()) {
    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;
  }
  mPrivacyObservers.AppendElement(weakObs);
  return NS_OK;
}

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

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;
  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->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 = mBrowsingContext->GetAllowContentRetargeting();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetAllowContentRetargeting(bool aAllowContentRetargeting) {
  mBrowsingContext->SetAllowContentRetargeting(aAllowContentRetargeting);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetAllowContentRetargetingOnChildren(
    bool* aAllowContentRetargetingOnChildren) {
  *aAllowContentRetargetingOnChildren =
      mBrowsingContext->GetAllowContentRetargetingOnChildren();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetAllowContentRetargetingOnChildren(
    bool aAllowContentRetargetingOnChildren) {
  mBrowsingContext->SetAllowContentRetargetingOnChildren(
      aAllowContentRetargetingOnChildren);
  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 = GetInProcessParentDocshell();
  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::GetMayEnableCharacterEncodingMenu(
    bool* aMayEnableCharacterEncodingMenu) {
  *aMayEnableCharacterEncodingMenu = false;
  if (!mContentViewer) {
    return NS_OK;
  }
  Document* doc = mContentViewer->GetDocument();
  if (!doc) {
    return NS_OK;
  }
  if (doc->WillIgnoreCharsetOverride()) {
    return NS_OK;
  }

  *aMayEnableCharacterEncodingMenu = true;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetCharsetAutodetected(bool* aCharsetAutodetected) {
  *aCharsetAutodetected = false;
  if (!mContentViewer) {
    return NS_OK;
  }
  Document* doc = mContentViewer->GetDocument();
  if (!doc) {
    return NS_OK;
  }
  int32_t source = doc->GetDocumentCharacterSetSource();

  if (source == kCharsetFromInitialAutoDetection ||
      source == kCharsetFromFinalAutoDetection ||
      source == kCharsetFromUserForcedAutoDetection) {
    *aCharsetAutodetected = true;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetAllDocShellsInSubtree(int32_t aItemType,
                                     DocShellEnumeratorDirection aDirection,
                                     nsTArray<RefPtr<nsIDocShell>>& aResult) {
  aResult.Clear();

  nsDocShellEnumerator docShellEnum(
      (aDirection == ENUMERATE_FORWARDS)
          ? nsDocShellEnumerator::EnumerationDirection::Forwards
          : nsDocShellEnumerator::EnumerationDirection::Backwards,
      aItemType, *this);

  nsresult rv = docShellEnum.BuildDocShellArray(aResult);
  if (NS_FAILED(rv)) {
    return rv;
  }

  return NS_OK;
}

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

NS_IMETHODIMP
nsDocShell::SetAppType(AppType 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::GetBusyFlags(BusyFlags* 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::GetLoadURIDelegate(nsILoadURIDelegate** aLoadURIDelegate) {
  nsCOMPtr<nsILoadURIDelegate> delegate = GetLoadURIDelegate();
  delegate.forget(aLoadURIDelegate);
  return NS_OK;
}

already_AddRefed<nsILoadURIDelegate> nsDocShell::GetLoadURIDelegate() {
  if (nsCOMPtr<nsILoadURIDelegate> result =
          do_QueryActor("LoadURIDelegate", GetWindow())) {
    return result.forget();
  }

  return nullptr;
}

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

NS_IMETHODIMP
nsDocShell::SetUseErrorPages(bool aUseErrorPages) {
  mUseErrorPages = aUseErrorPages;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetPreviousEntryIndex(int32_t* aPreviousEntryIndex) {
  *aPreviousEntryIndex = mPreviousEntryIndex;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetLoadedEntryIndex(int32_t* aLoadedEntryIndex) {
  *aLoadedEntryIndex = mLoadedEntryIndex;
  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.
  mPreviousEntryIndex = std::max(-1, mPreviousEntryIndex - aNumEntries);
  mLoadedEntryIndex = std::max(0, mLoadedEntryIndex - aNumEntries);

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

  return NS_OK;
}

void nsDocShell::TriggerParentCheckDocShellIsEmpty() {
  if (RefPtr<nsDocShell> parent = GetInProcessParentDocshell()) {
    parent->DocLoaderIsEmpty(true);
  }
  if (GetBrowsingContext()->IsContentSubframe() &&
      !GetBrowsingContext()->GetParent()->IsInProcess()) {
    if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) {
      mozilla::Unused << browserChild->SendMaybeFireEmbedderLoadEvents(
          EmbedderElementEventType::NoEvent);
    }
  }
}

nsresult nsDocShell::HistoryEntryRemoved(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 == mPreviousEntryIndex) {
    mPreviousEntryIndex = -1;
  } else if (aIndex < mPreviousEntryIndex) {
    --mPreviousEntryIndex;
  }
  if (mLoadedEntryIndex == aIndex) {
    mLoadedEntryIndex = 0;
  } else if (aIndex < mLoadedEntryIndex) {
    --mLoadedEntryIndex;
  }

  nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
  while (iter.HasMore()) {
    nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext());
    if (shell) {
      static_cast<nsDocShell*>(shell.get())->HistoryEntryRemoved(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) {
  *aWhen = (TimeStamp::Now() - TimeStamp::ProcessCreation()).ToMilliseconds();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetWindowDraggingAllowed(bool aValue) {
  RefPtr<nsDocShell> parent = GetInProcessParentDocshell();
  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 = GetInProcessParentDocshell();
  if (mItemType == typeChrome && !parent) {
    // Top level chrome window
    *aValue = true;
  } else {
    *aValue = mWindowDraggingAllowed;
  }
  return NS_OK;
}

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

nsIChannel* nsDocShell::GetCurrentDocChannel() {
  if (mContentViewer) {
    Document* 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;
  }
  mScrollObservers.AppendElement(weakObs);
  return NS_OK;
}

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 = mBrowsingContext->Name();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetName(const nsAString& aName) {
  mBrowsingContext->SetName(aName);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::NameEquals(const nsAString& aName, bool* aResult) {
  NS_ENSURE_ARG_POINTER(aResult);
  *aResult = mBrowsingContext->NameEquals(aName);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetCustomUserAgent(nsAString& aCustomUserAgent) {
  mBrowsingContext->GetCustomUserAgent(aCustomUserAgent);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetCustomUserAgent(const nsAString& aCustomUserAgent) {
  if (mWillChangeProcess) {
    NS_WARNING("SetCustomUserAgent: Process is changing. Ignoring set");
    return NS_ERROR_FAILURE;
  }

  mBrowsingContext->SetCustomUserAgent(aCustomUserAgent);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::ClearCachedUserAgent() {
  RefPtr<nsGlobalWindowInner> win =
      mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr;
  if (win) {
    Navigator* navigator = win->Navigator();
    if (navigator) {
      navigator->ClearUserAgentCache();
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetTouchEventsOverride(TouchEventsOverride* aTouchEventsOverride) {
  *aTouchEventsOverride = mTouchEventsOverride;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetTouchEventsOverride(TouchEventsOverride aTouchEventsOverride) {
  // We don't have a way to verify this coming from Javascript, so this check is
  // still needed.
  if (!(aTouchEventsOverride == TOUCHEVENTS_OVERRIDE_NONE ||
        aTouchEventsOverride == TOUCHEVENTS_OVERRIDE_ENABLED ||
        aTouchEventsOverride == 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;
}

NS_IMETHODIMP
nsDocShell::GetMetaViewportOverride(
    MetaViewportOverride* aMetaViewportOverride) {
  NS_ENSURE_ARG_POINTER(aMetaViewportOverride);

  *aMetaViewportOverride = mMetaViewportOverride;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetMetaViewportOverride(
    MetaViewportOverride aMetaViewportOverride) {
  // We don't have a way to verify this coming from Javascript, so this check is
  // still needed.
  if (!(aMetaViewportOverride == META_VIEWPORT_OVERRIDE_NONE ||
        aMetaViewportOverride == META_VIEWPORT_OVERRIDE_ENABLED ||
        aMetaViewportOverride == META_VIEWPORT_OVERRIDE_DISABLED)) {
    return NS_ERROR_INVALID_ARG;
  }

  mMetaViewportOverride = aMetaViewportOverride;

  // Inform our presShell that it needs to re-check its need for a viewport
  // override.
  if (RefPtr<PresShell> presShell = GetPresShell()) {
    presShell->UpdateViewportOverridden(true);
  }

  return NS_OK;
}

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

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

  MOZ_DIAGNOSTIC_ASSERT(
      (mBrowsingContext->IsContent() ? typeContent : typeChrome) == mItemType);
  *aItemType = mItemType;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetInProcessParent(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;
}

// With Fission, related nsDocShell objects may exist in a different process. In
// that case, this method will return `nullptr`, despite a parent nsDocShell
// object existing.
//
// Prefer using `BrowsingContext::Parent()`, which will succeed even if the
// parent entry is not in the current process, and handle the case where the
// parent nsDocShell is inaccessible.
already_AddRefed<nsDocShell> nsDocShell::GetInProcessParentDocshell() {
  nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(GetAsSupports(mParent));
  return docshell.forget().downcast<nsDocShell>();
}

void nsDocShell::MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal) {
  MOZ_ASSERT(!mIsBeingDestroyed);

  // If there is an existing document then there is no need to create
  // a client for a future initial about:blank document.
  if (mScriptGlobal && mScriptGlobal->GetCurrentInnerWindowInternal() &&
      mScriptGlobal->GetCurrentInnerWindowInternal()->GetExtantDoc()) {
    MOZ_DIAGNOSTIC_ASSERT(mScriptGlobal->GetCurrentInnerWindowInternal()
                              ->GetClientInfo()
                              .isSome());
    MOZ_DIAGNOSTIC_ASSERT(!mInitialClientSource);
    return;
  }

  // Don't recreate the initial client source.  We call this multiple times
  // when DoChannelLoad() is called before CreateAboutBlankContentViewer.
  if (mInitialClientSource) {
    return;
  }

  // Don't pre-allocate the client when we are sandboxed.  The inherited
  // principal does not take sandboxing into account.
  // TODO: Refactor sandboxing principal code out so we can use it here.
  if (!aPrincipal && mBrowsingContext->GetSandboxFlags()) {
    return;
  }

  nsIPrincipal* principal =
      aPrincipal ? aPrincipal : GetInheritedPrincipal(false);

  // Sometimes there is no principal available when we are called from
  // CreateAboutBlankContentViewer.  For example, sometimes the principal
  // is only extracted from the load context after the document is created
  // in Document::ResetToURI().  Ideally we would do something similar
  // here, but for now lets just avoid the issue by not preallocating the
  // client.
  if (!principal) {
    return;
  }

  nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
  if (!win) {
    return;
  }

  mInitialClientSource = ClientManager::CreateSource(
      ClientType::Window, win->EventTargetFor(TaskCategory::Other), principal);
  MOZ_DIAGNOSTIC_ASSERT(mInitialClientSource);

  // Mark the initial client as execution ready, but owned by the docshell.
  // If the client is actually used this will cause ClientSource to force
  // the creation of the initial about:blank by calling
  // nsDocShell::GetDocument().
  mInitialClientSource->DocShellExecutionReady(this);

  // Next, check to see if the parent is controlled.
  nsCOMPtr<nsIDocShell> parent = GetInProcessParentDocshell();
  nsPIDOMWindowOuter* parentOuter = parent ? parent->GetWindow() : nullptr;
  nsPIDOMWindowInner* parentInner =
      parentOuter ? parentOuter->GetCurrentInnerWindow() : nullptr;
  if (!parentInner) {
    return;
  }

  nsCOMPtr<nsIURI> uri;
  MOZ_ALWAYS_SUCCEEDS(
      NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("about:blank")));

  // We're done if there is no parent controller or if this docshell
  // is not permitted to control for some reason.
  Maybe<ServiceWorkerDescriptor> controller(parentInner->GetController());
  if (controller.isNothing() ||
      !ServiceWorkerAllowedToControlWindow(principal, uri)) {
    return;
  }

  mInitialClientSource->InheritController(controller.ref());
}

Maybe<ClientInfo> nsDocShell::GetInitialClientInfo() const {
  if (mInitialClientSource) {
    Maybe<ClientInfo> result;
    result.emplace(mInitialClientSource->Info());
    return result;
  }

  nsGlobalWindowInner* innerWindow =
      mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr;
  Document* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr;

  if (!doc || !doc->IsInitialDocument()) {
    return Maybe<ClientInfo>();
  }

  return innerWindow->GetClientInfo();
}

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

  // If we have no tree owner, that means that we've been detached from the
  // docshell tree (this is distinct from having no parent docshell, 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();

  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;
  nsCOMPtr<nsIDocShell> parentAsDocShell(do_QueryInterface(parent));

  if (parentAsDocShell) {
    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);
    }
    if (NS_SUCCEEDED(parentAsDocShell->GetIsActive(&value))) {
      SetIsActive(value);
    }
    if (NS_FAILED(parentAsDocShell->GetAllowDNSPrefetch(&value))) {
      value = false;
    }
    SetAllowDNSPrefetch(mAllowDNSPrefetch && value);
    SetAffectPrivateSessionLifetime(
        parentAsDocShell->GetAffectPrivateSessionLifetime());

    SetTouchEventsOverride(parentAsDocShell->GetTouchEventsOverride());

    // We don't need to inherit metaViewportOverride, because the viewport
    // is only relevant for the outermost nsDocShell, not for any iframes
    // like this that might be embedded within it.
  }

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

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

  // Inform windows when they're being removed from their parent.
  if (!aParent) {
    MaybeClearStorageAccessFlag();
  }

  return NS_OK;
}

void nsDocShell::MaybeClearStorageAccessFlag() {
  if (mScriptGlobal) {
    // Tell our window that the parent has now changed.
    mScriptGlobal->ParentWindowChanged();

    // Tell all of our children about the change recursively as well.
    nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
    while (iter.HasMore()) {
      nsCOMPtr<nsIDocShell> child = do_QueryObject(iter.GetNext());
      if (child) {
        static_cast<nsDocShell*>(child.get())->MaybeClearStorageAccessFlag();
      }
    }
  }
}

NS_IMETHODIMP
nsDocShell::GetInProcessSameTypeParent(nsIDocShellTreeItem** aParent) {
  if (BrowsingContext* parentBC = mBrowsingContext->GetParent()) {
    *aParent = do_AddRef(parentBC->GetDocShell()).take();
  }
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetSameTypeInProcessParentIgnoreBrowserBoundaries(
    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::GetInProcessRootTreeItem(nsIDocShellTreeItem** aRootTreeItem) {
  NS_ENSURE_ARG_POINTER(aRootTreeItem);

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

  root.forget(aRootTreeItem);
  return NS_OK;
}

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

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

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

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

NS_IMETHODIMP
nsDocShell::SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner) {
  if (mIsBeingDestroyed && aTreeOwner) {
    return NS_ERROR_FAILURE;
  }

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

  // If we're in the content process and have had a TreeOwner set on us, extract
  // our BrowserChild actor. If we've already had our BrowserChild set, assert
  // that it hasn't changed.
  if (mTreeOwner && XRE_IsContentProcess()) {
    nsCOMPtr<nsIBrowserChild> newBrowserChild = do_GetInterface(mTreeOwner);
    MOZ_ASSERT(newBrowserChild,
               "No BrowserChild actor for tree owner in Content!");

    if (mBrowserChild) {
      nsCOMPtr<nsIBrowserChild> oldBrowserChild =
          do_QueryReferent(mBrowserChild);
      MOZ_RELEASE_ASSERT(
          oldBrowserChild == newBrowserChild,
          "Cannot cahnge BrowserChild during nsDocShell lifetime!");
    } else {
      mBrowserChild = do_GetWeakReference(newBrowserChild);
    }
  }

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

void nsDocShell::SetChildOffset(int32_t aChildOffset) {
  mChildOffset = aChildOffset;
}

int32_t nsDocShell::GetChildOffset() { return mChildOffset; }

NS_IMETHODIMP
nsDocShell::GetHistoryID(nsID** aID) {
  *aID = mHistoryID.Clone();
  return NS_OK;
}

const nsID nsDocShell::HistoryID() { return mHistoryID; }

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

NS_IMETHODIMP
nsDocShell::GetInProcessChildCount(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 (mBrowsingContext->GetUseGlobalHistory()) {
    // childDocShell->SetUseGlobalHistory(true);
    // this should be set through BC inherit
    MOZ_ASSERT(nsDocShell::Cast(childDocShell)
                   ->mBrowsingContext->GetUseGlobalHistory());
  }

  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;
  }
  Document* doc = mContentViewer->GetDocument();
  if (!doc) {
    return NS_OK;
  }

  const Encoding* 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::GetInProcessChildAt(int32_t aIndex, nsIDocShellTreeItem** aChild) {
  NS_ENSURE_ARG_POINTER(aChild);

  RefPtr<nsDocShell> child = GetInProcessChildAt(aIndex);
  NS_ENSURE_TRUE(child, NS_ERROR_UNEXPECTED);

  child.forget(aChild);

  return NS_OK;
}

nsDocShell* nsDocShell::GetInProcessChildAt(int32_t aIndex) {
#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);

  // child may be nullptr here.
  return static_cast<nsDocShell*>(child);
}

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
     */
    if (NS_FAILED(mLSHE->ReplaceChild(aNewEntry))) {
      rv = mLSHE->AddChild(aNewEntry, aChildOffset);
    }
  } else if (!aCloneRef) {
    /* This is an initial load in some subframe.  Just append it if we can */
    if (mOSHE) {
      rv = mOSHE->AddChild(aNewEntry, aChildOffset, UseRemoteSubframes());
    }
  } 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 (GetSessionHistory()) {
    rv = GetSessionHistory()->LegacySHistory()->AddChildSHEntryHelper(
        aCloneRef, aNewEntry, mBrowsingContext, aCloneChildren);
  } 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
  RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
  if (rootSH) {
    mPreviousEntryIndex = rootSH->Index();
  }

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

  if (rootSH) {
    mLoadedEntryIndex = rootSH->Index();

    if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
      MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
              ("Previous index: %d, Loaded index: %d", mPreviousEntryIndex,
               mLoadedEntryIndex));
    }
  }

  return rv;
}

NS_IMETHODIMP
nsDocShell::RemoveFromSessionHistory() {
  RefPtr<ChildSHistory> sessionHistory =
      mBrowsingContext->Top()->GetChildSessionHistory();
  if (!sessionHistory) {
    return NS_OK;
  }
  int32_t index = sessionHistory->Index();
  AutoTArray<nsID, 16> ids({mHistoryID});
  sessionHistory->LegacySHistory()->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;
}

NS_IMETHODIMP nsDocShell::SynchronizeLayoutHistoryState() {
  if (mOSHE) {
    mOSHE->SynchronizeLayoutHistoryState();
  }
  return NS_OK;
}

void nsDocShell::SetLoadGroupDefaultLoadFlags(nsLoadFlags aLoadFlags) {
  if (mLoadGroup) {
    mLoadGroup->SetDefaultLoadFlags(aLoadFlags);
  } else {
    NS_WARNING(
        "nsDocShell::SetLoadGroupDefaultLoadFlags has no loadGroup to "
        "propagate the mode to");
  }
}

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

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

Document* nsDocShell::GetExtantDocument() {
  return mContentViewer ? mContentViewer->GetDocument() : nullptr;
}

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

NS_IMETHODIMP
nsDocShell::GetDomWindow(mozIDOMWindowProxy** aWindow) {
  NS_ENSURE_ARG_POINTER(aWindow);

  nsresult rv = EnsureScriptEnvironment();
  NS_ENSURE_SUCCESS(rv, rv);

  RefPtr<nsGlobalWindowOuter> window = mScriptGlobal;
  window.forget(aWindow);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) {
  RefPtr<ContentFrameMessageManager> mm;
  if (RefPtr<BrowserChild> browserChild = BrowserChild::GetFrom(this)) {
    mm = browserChild->GetMessageManager();
  } else if (nsPIDOMWindowOuter* win = GetWindow()) {
    mm = win->GetMessageManager();
  }
  mm.forget(aMessageManager);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetIsNavigating(bool* aOut) {
  *aOut = mIsNavigating;
  return NS_OK;
}

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

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

void nsDocShell::ClearFrameHistory(nsISHEntry* aEntry) {
  RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
  if (!rootSH || !aEntry) {
    return;
  }

  rootSH->LegacySHistory()->RemoveFrameEntries(aEntry);
}

//-------------------------------------
//-- 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) {
  *aCanGoBack = false;
  if (!IsNavigationAllowed(false)) {
    return NS_OK;  // JS may not handle returning of an error code
  }
  RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
  if (rootSH) {
    *aCanGoBack = rootSH->CanGo(-1);
    return NS_OK;
  }
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsDocShell::GetCanGoForward(bool* aCanGoForward) {
  *aCanGoForward = false;
  if (!IsNavigationAllowed(false)) {
    return NS_OK;  // JS may not handle returning of an error code
  }
  RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
  if (rootSH) {
    *aCanGoForward = rootSH->CanGo(1);
    return NS_OK;
  }
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsDocShell::GoBack() {
  if (!IsNavigationAllowed()) {
    return NS_OK;  // JS may not handle returning of an error code
  }

  auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; });
  mIsNavigating = true;

  RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
  NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE);
  ErrorResult rv;
  rootSH->Go(-1, rv);
  return rv.StealNSResult();
}

NS_IMETHODIMP
nsDocShell::GoForward() {
  if (!IsNavigationAllowed()) {
    return NS_OK;  // JS may not handle returning of an error code
  }

  auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; });
  mIsNavigating = true;

  RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
  NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE);
  ErrorResult rv;
  rootSH->Go(1, rv);
  return rv.StealNSResult();
}

// XXX(nika): We may want to stop exposing this API in the child process? Going
// to a specific index from multiple different processes could definitely race.
NS_IMETHODIMP
nsDocShell::GotoIndex(int32_t aIndex) {
  if (!IsNavigationAllowed()) {
    return NS_OK;  // JS may not handle returning of an error code
  }

  auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; });
  mIsNavigating = true;

  RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
  NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE);
  return rootSH->LegacySHistory()->GotoIndex(aIndex);
}

nsresult nsDocShell::LoadURI(const nsAString& aURI,
                             const LoadURIOptions& aLoadURIOptions) {
  if (!IsNavigationAllowed()) {
    return NS_OK;  // JS may not handle returning of an error code
  }

  RefPtr<nsDocShellLoadState> loadState;
  nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions(
      mBrowsingContext, aURI, aLoadURIOptions, getter_AddRefs(loadState));

  uint32_t loadFlags = aLoadURIOptions.mLoadFlags;
  if (NS_ERROR_MALFORMED_URI == rv) {
    if (DisplayLoadError(rv, nullptr, PromiseFlatString(aURI).get(), nullptr) &&
        (loadFlags & LOAD_FLAGS_ERROR_LOAD_CHANGES_RV) != 0) {
      return NS_ERROR_LOAD_SHOWED_ERRORPAGE;
    }
  }

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

  return LoadURI(loadState, true);
}

NS_IMETHODIMP
nsDocShell::LoadURIFromScript(const nsAString& aURI,
                              JS::Handle<JS::Value> aLoadURIOptions,
                              JSContext* aCx) {
  // generate dictionary for aLoadURIOptions and forward call
  LoadURIOptions loadURIOptions;
  if (!loadURIOptions.Init(aCx, aLoadURIOptions)) {
    return NS_ERROR_INVALID_ARG;
  }
  return LoadURI(aURI, loadURIOptions);
}

void nsDocShell::UnblockEmbedderLoadEventForFailure(bool aFireFrameErrorEvent) {
  // If we're not in a content frame, or are at a BrowsingContext tree boundary,
  // such as the content-chrome boundary, don't fire the error event.
  if (mBrowsingContext->IsTopContent() || mBrowsingContext->IsChrome()) {
    return;
  }

  // If embedder is same-process, then unblocking the load event is already
  // handled by nsDocLoader. Fire the error event on our embedder element if
  // requested.
  //
  // XXX: Bug 1440212 is looking into potentially changing this behaviour to act
  // more like the remote case when in-process.
  RefPtr<Element> element = mBrowsingContext->GetEmbedderElement();
  if (element) {
    if (aFireFrameErrorEvent) {
      if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(element)) {
        if (RefPtr<nsFrameLoader> fl = flo->GetFrameLoader()) {
          fl->FireErrorEvent();
        }
      }
    }
    return;
  }

  // If we have a cross-process parent document, we must notify it that we no
  // longer block its load event.  This is necessary for OOP sub-documents
  // because error documents do not result in a call to
  // SendMaybeFireEmbedderLoadEvents via any of the normal call paths.
  // (Obviously, we must do this before any of the returns below.)
  RefPtr<BrowserChild> browserChild = BrowserChild::GetFrom(this);
  if (browserChild) {
    mozilla::Unused << browserChild->SendMaybeFireEmbedderLoadEvents(
        aFireFrameErrorEvent ? EmbedderElementEventType::ErrorEvent
                             : EmbedderElementEventType::NoEvent);
  }
}

NS_IMETHODIMP
nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
                             const char16_t* aURL, nsIChannel* aFailedChannel,
                             bool* aDisplayedErrorPage) {
  MOZ_LOG(gDocShellLeakLog, LogLevel::Debug,
          ("DOCSHELL %p DisplayLoadError %s\n", this,
           aURI ? aURI->GetSpecOrDefault().get() : ""));

  *aDisplayedErrorPage = false;
  // Get prompt and string bundle services
  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);

  const char* error = nullptr;
  // The key used to select the appropriate error message from the properties
  // file.
  const char* errorDescriptionID = nullptr;
  AutoTArray<nsString, 3> formatStrs;
  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.AppendElement());
    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);
    }
    error = "unknownProtocolFound";
  } else if (NS_ERROR_FILE_NOT_FOUND == aError) {
    NS_ENSURE_ARG_POINTER(aURI);
    error = "fileNotFound";
  } else if (NS_ERROR_FILE_ACCESS_DENIED == aError) {
    NS_ENSURE_ARG_POINTER(aURI);
    error = "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.AppendElement());
    errorDescriptionID = "dnsNotFound2";
    error = "dnsNotFound";
  } else if (NS_ERROR_CONNECTION_REFUSED == aError ||
             NS_ERROR_PROXY_BAD_GATEWAY == aError) {
    NS_ENSURE_ARG_POINTER(aURI);
    addHostPort = true;
    error = "connectionFailure";
  } else if (NS_ERROR_NET_INTERRUPT == aError) {
    NS_ENSURE_ARG_POINTER(aURI);
    addHostPort = true;
    error = "netInterrupt";
  } else if (NS_ERROR_NET_TIMEOUT == aError ||
             NS_ERROR_PROXY_GATEWAY_TIMEOUT == aError) {
    NS_ENSURE_ARG_POINTER(aURI);
    // Get the host
    nsAutoCString host;
    aURI->GetHost(host);
    CopyUTF8toUTF16(host, *formatStrs.AppendElement());
    error = "netTimeout";
  } else if (NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION == aError ||
             NS_ERROR_CSP_FORM_ACTION_VIOLATION == aError ||
             NS_ERROR_CSP_NAVIGATE_TO_VIOLATION == aError) {
    // CSP error
    cssClass.AssignLiteral("neterror");
    error = "cspBlocked";
  } else if (NS_ERROR_XFO_VIOLATION == aError) {
    // XFO error
    cssClass.AssignLiteral("neterror");
    error = "xfoBlocked";
  } 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 = "sslv3Used";
        addHostPort = true;
      } else if (securityState &
                 nsIWebProgressListener::STATE_USES_WEAK_CRYPTO) {
        error = "weakCryptoUsed";
        addHostPort = true;
      }
    } else {
      // No channel, let's obtain the generic error message
      if (nsserr) {
        nsserr->GetErrorMessage(aError, messageStr);
      }
    }
    // We don't have a message string here anymore but DisplayLoadError
    // requires a non-empty messageStr.
    messageStr.Truncate();
    messageStr.AssignLiteral(u" ");
    if (errorClass == nsINSSErrorsService::ERROR_CLASS_BAD_CERT) {
      error = "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).
      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,
                              GetOriginAttributes(), nullptr, nullptr,
                              &isStsHost);
        NS_ENSURE_SUCCESS(rv, rv);
        rv = sss->IsSecureURI(nsISiteSecurityService::STATIC_PINNING, aURI,
                              flags, GetOriginAttributes(), nullptr, nullptr,
                              &isPinnedHost);
        NS_ENSURE_SUCCESS(rv, rv);
      } else {
        mozilla::dom::ContentChild* cc =
            mozilla::dom::ContentChild::GetSingleton();
        cc->SendIsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags,
                            GetOriginAttributes(), &isStsHost);
        cc->SendIsSecureURI(nsISiteSecurityService::STATIC_PINNING, aURI, flags,
                            GetOriginAttributes(), &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");
      }

      // See if an alternate cert error page is registered
      nsAutoCString alternateErrorPage;
      nsresult rv = Preferences::GetCString(
          "security.alternate_certificate_error_page", alternateErrorPage);
      if (NS_SUCCEEDED(rv)) {
        errorPage.Assign(alternateErrorPage);
      }
    } else {
      error = "nssFailure2";
    }
  } else if (NS_ERROR_PHISHING_URI == aError ||
             NS_ERROR_MALWARE_URI == aError ||
             NS_ERROR_UNWANTED_URI == aError ||
             NS_ERROR_HARMFUL_URI == aError) {
    nsAutoCString host;
    aURI->GetHost(host);
    CopyUTF8toUTF16(host, *formatStrs.AppendElement());

    // 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
    nsAutoCString alternateErrorPage;
    nsresult rv = Preferences::GetCString("urlclassifier.alternate_error_page",
                                          alternateErrorPage);
    if (NS_SUCCEEDED(rv)) {
      errorPage.Assign(alternateErrorPage);
    }

    if (NS_ERROR_PHISHING_URI == aError) {
      error = "deceptiveBlocked";
    } else if (NS_ERROR_MALWARE_URI == aError) {
      error = "malwareBlocked";
    } else if (NS_ERROR_UNWANTED_URI == aError) {
      error = "unwantedBlocked";
    } else if (NS_ERROR_HARMFUL_URI == aError) {
      error = "harmfulBlocked";
    }

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

    RefPtr<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 if (NS_ERROR_FRAME_CRASHED == aError) {
    errorPage.AssignLiteral("framecrashed");
    error = "framecrashed";
    messageStr.AssignLiteral(u" ");
  } else if (NS_ERROR_BUILDID_MISMATCH == aError) {
    errorPage.AssignLiteral("restartrequired");
    error = "restartrequired";

    // 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 = "malformedURI";
        errorDescriptionID = "malformedURI2";
        break;
      case NS_ERROR_REDIRECT_LOOP:
        // Doc failed to load because the server generated too many redirects
        error = "redirectLoop";
        break;
      case NS_ERROR_UNKNOWN_SOCKET_TYPE:
        // Doc failed to load because PSM is not installed
        error = "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 = "netReset";
        break;
      case NS_ERROR_DOCUMENT_NOT_CACHED:
        // Doc failed to load because the cache does not contain a copy of
        // the document.
        error = "notCached";
        break;
      case NS_ERROR_OFFLINE:
        // Doc failed to load because we are offline.
        error = "netOffline";
        break;
      case NS_ERROR_DOCUMENT_IS_PRINTMODE:
        // Doc navigation attempted while Printing or Print Preview
        error = "isprinting";
        break;
      case NS_ERROR_PORT_ACCESS_NOT_ALLOWED:
        // Port blocked for security reasons
        addHostPort = true;
        error = "deniedPortAccess";
        break;
      case NS_ERROR_UNKNOWN_PROXY_HOST:
        // Proxy hostname could not be resolved.
        error = "proxyResolveFailure";
        break;
      case NS_ERROR_PROXY_CONNECTION_REFUSED:
      case NS_ERROR_PROXY_FORBIDDEN:
      case NS_ERROR_PROXY_NOT_IMPLEMENTED:
      case NS_ERROR_PROXY_AUTHENTICATION_FAILED:
      case NS_ERROR_PROXY_TOO_MANY_REQUESTS:
        // Proxy connection was refused.
        error = "proxyConnectFailure";
        break;
      case NS_ERROR_INVALID_CONTENT_ENCODING:
        // Bad Content Encoding.
        error = "contentEncodingError";
        break;
      case NS_ERROR_REMOTE_XUL:
        error = "remoteXUL";
        break;
      case NS_ERROR_UNSAFE_CONTENT_TYPE:
        // Channel refused to load from an unrecognized content type.
        error = "unsafeContentType";
        break;
      case NS_ERROR_CORRUPTED_CONTENT:
        // Broken Content Detected. e.g. Content-MD5 check failure.
        error = "corruptedContentErrorv2";
        break;
      case NS_ERROR_INTERCEPTION_FAILED:
        // ServiceWorker intercepted request, but something went wrong.
        error = "corruptedContentErrorv2";
        break;
      case NS_ERROR_NET_INADEQUATE_SECURITY:
        // Server negotiated bad TLS for HTTP/2.
        error = "inadequateSecurityError";
        addHostPort = true;
        break;
      case NS_ERROR_BLOCKED_BY_POLICY:
        // Page blocked by policy
        error = "blockedByPolicy";
        break;
      case NS_ERROR_NET_HTTP2_SENT_GOAWAY:
      case NS_ERROR_NET_HTTP3_PROTOCOL_ERROR:
        // HTTP/2 or HTTP/3 stack detected a protocol error
        error = "networkProtocolError";
        break;

      default:
        break;
    }
  }

  // If the HTTPS-Only Mode upgraded this request and the upgrade might have
  // caused this error, we replace the error-page with about:httpsonlyerror
  if (aFailedChannel && nsHTTPSOnlyUtils::CouldBeHttpsOnlyError(aError)) {
    nsCOMPtr<nsILoadInfo> loadInfo = aFailedChannel->LoadInfo();
    uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
    if ((httpsOnlyStatus &
         nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED) &&
        !(httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT)) {
      errorPage.AssignLiteral("httpsonlyerror");
    }
  }

  if (nsCOMPtr<nsILoadURIDelegate> loadURIDelegate = GetLoadURIDelegate()) {
    nsCOMPtr<nsIURI> errorPageURI;
    rv = loadURIDelegate->HandleLoadError(aURI, aError,
                                          NS_ERROR_GET_MODULE(aError),
                                          getter_AddRefs(errorPageURI));
    // If the docshell is going away there's no point in showing an error page.
    if (NS_FAILED(rv) || mIsBeingDestroyed) {
      *aDisplayedErrorPage = false;
      return NS_OK;
    }

    if (errorPageURI) {
      *aDisplayedErrorPage =
          NS_SUCCEEDED(LoadErrorPage(errorPageURI, aURI, aFailedChannel));
      return NS_OK;
    }
  }

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

  if (!errorDescriptionID) {
    errorDescriptionID = error;
  }

  Telemetry::AccumulateCategoricalKeyed(
      IsFrame() ? NS_LITERAL_CSTRING("frame") : NS_LITERAL_CSTRING("top"),
      mozilla::dom::LoadErrorToTelemetryLabel(aError));

  // 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.AppendElement());
    }

    nsAutoCString spec;
    rv = NS_ERROR_NOT_AVAILABLE;
    auto& nextFormatStr = *formatStrs.AppendElement();
    if (aURI) {
      // displaying "file://" is aesthetically unpleasing and could even be
      // confusing to the user
      if (SchemeIsFile(aURI)) {
        aURI->GetPathQueryRef(spec);
      } else {
        aURI->GetSpec(spec);
      }

      nsCOMPtr<nsITextToSubURI> textToSubURI(
          do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv));
      if (NS_SUCCEEDED(rv)) {
        rv = textToSubURI->UnEscapeURIForUI(spec, nextFormatStr);
      }
    } else {
      spec.Assign('?');
    }
    if (NS_FAILED(rv)) {
      CopyUTF8toUTF16(spec, nextFormatStr);
    }
    rv = NS_OK;

    nsAutoString str;
    rv =
        stringBundle->FormatStringFromName(errorDescriptionID, formatStrs, str);
    NS_ENSURE_SUCCESS(rv, rv);
    messageStr.Assign(str);
  }

  // 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) &&
      SchemeIsHTTPS(aURI)) {
    // Maybe TLS intolerant. Treat this as an SSL error.
    error = "nssFailure2";
  }

  if (mUseErrorPages) {
    // Display an error page
    nsresult loadedPage =
        LoadErrorPage(aURI, aURL, errorPage.get(), error, 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"

nsresult nsDocShell::LoadErrorPage(nsIURI* aURI, const char16_t* aURL,
                                   const char* aErrorPage,
                                   const char* aErrorType,
                                   const char16_t* aDescription,
                                   const char* aCSSClass,
                                   nsIChannel* aFailedChannel) {
  MOZ_ASSERT(!mIsBeingDestroyed);

#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 ? aURI->GetSpecOrDefault().get() : "",
             NS_ConvertUTF16toUTF8(aURL).get(), chanName.get()));
  }
#endif

  nsAutoCString url;
  if (aURI) {
    nsresult rv = aURI->GetSpec(url);
    NS_ENSURE_SUCCESS(rv, rv);
  } else if (aURL) {
    CopyUTF16toUTF8(MakeStringSpan(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, escapedError, escapedDescription, escapedCSSClass;
  SAFE_ESCAPE(escapedUrl, url, url_Path);
  SAFE_ESCAPE(escapedError, nsDependentCString(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=UTF-8");

  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;
  nsresult rv = NS_NewURI(getter_AddRefs(errorPageURI), errorPageUrl);
  NS_ENSURE_SUCCESS(rv, rv);

  return LoadErrorPage(errorPageURI, aURI, aFailedChannel);
}

nsresult nsDocShell::LoadErrorPage(nsIURI* aErrorURI, nsIURI* aFailedURI,
                                   nsIChannel* aFailedChannel) {
  mFailedChannel = aFailedChannel;
  mFailedURI = aFailedURI;
  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();
  }

  RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aErrorURI);
  loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal());
  loadState->SetLoadType(LOAD_ERROR_PAGE);
  loadState->SetFirstParty(true);
  loadState->SetSourceBrowsingContext(mBrowsingContext);
  loadState->SetHasValidUserGestureActivation(
      mBrowsingContext &&
      mBrowsingContext->HasValidTransientUserGestureActivation());

  return InternalLoad(loadState, 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 & INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 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
  RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
  bool canReload = true;
  if (rootSH) {
    rootSH->LegacySHistory()->NotifyOnHistoryReload(&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 {
    RefPtr<Document> doc(GetDocument());

    if (!doc) {
      return NS_OK;
    }

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

    nsIPrincipal* triggeringPrincipal = doc->NodePrincipal();
    nsCOMPtr<nsIContentSecurityPolicy> csp = doc->GetCsp();

    nsAutoString contentTypeHint;
    doc->GetContentType(contentTypeHint);

    if (doc->IsSrcdocDocument()) {
      doc->GetSrcdocData(srcdoc);
      flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC;
      baseURI = doc->GetBaseURI();
    } else {
      srcdoc = VoidString();
    }
    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));
      }

      nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
      loadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI));
    }

    if (!triggeringPrincipal) {
      MOZ_ASSERT(false, "Reload needs a valid triggeringPrincipal");
      return NS_ERROR_FAILURE;
    }

    // Stack variables to ensure changes to the member variables don't affect to
    // the call.
    nsCOMPtr<nsIURI> currentURI = mCurrentURI;

    // Reload always rewrites result principal URI.
    Maybe<nsCOMPtr<nsIURI>> emplacedResultPrincipalURI;
    emplacedResultPrincipalURI.emplace(std::move(resultPrincipalURI));

    RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(currentURI);
    loadState->SetReferrerInfo(mReferrerInfo);
    loadState->SetOriginalURI(originalURI);
    loadState->SetMaybeResultPrincipalURI(emplacedResultPrincipalURI);
    loadState->SetLoadReplace(loadReplace);
    loadState->SetTriggeringPrincipal(triggeringPrincipal);
    loadState->SetPrincipalToInherit(triggeringPrincipal);
    loadState->SetCsp(csp);
    loadState->SetLoadFlags(flags);
    loadState->SetTypeHint(NS_ConvertUTF16toUTF8(contentTypeHint));
    loadState->SetLoadType(loadType);
    loadState->SetFirstParty(true);
    loadState->SetSrcdocData(srcdoc);
    loadState->SetSourceBrowsingContext(mBrowsingContext);
    loadState->SetBaseURI(baseURI);
    loadState->SetHasValidUserGestureActivation(
        mBrowsingContext &&
        mBrowsingContext->HasValidTransientUserGestureActivation());
    rv = InternalLoad(loadState, nullptr, nullptr);
  }

  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
      SetHistoryEntryAndUpdateBC(Some(nullptr), Some<nsISHEntry*>(mLSHE));
    }

    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(Document** aDocument) {
  NS_ENSURE_ARG_POINTER(aDocument);
  NS_ENSURE_SUCCESS(EnsureContentViewer(), NS_ERROR_FAILURE);

  RefPtr<Document> doc = mContentViewer->GetDocument();
  if (!doc) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  doc.forget(aDocument);
  return NS_OK;
}

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

  nsCOMPtr<nsIURI> uri = mCurrentURI;
  uri.forget(aURI);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetSessionHistoryXPCOM(nsISupports** aSessionHistory) {
  NS_ENSURE_ARG_POINTER(aSessionHistory);
  RefPtr<ChildSHistory> shistory = GetSessionHistory();
  shistory.forget(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) {
    nsCString spec, newSpec;

    // Create a new view-source URI and replace the original.
    nsCOMPtr<nsIURI> oldUri = shEntry->GetURI();

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

    nsCOMPtr<nsIURI> newUri;
    rv = NS_NewURI(getter_AddRefs(newUri), newSpec);
    if (NS_FAILED(rv)) {
      return rv;
    }
    shEntry->SetURI(newUri);
    shEntry->SetOriginalURI(nullptr);
    shEntry->SetResultPrincipalURI(nullptr);
    // shEntry's current triggering principal is whoever loaded that page
    // initially. But now we're doing another load of the page, via an API that
    // is only exposed to system code.  The triggering principal for this load
    // should be the system principal.
    shEntry->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal());
  }

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

NS_IMETHODIMP
nsDocShell::GetCurrentDescriptor(nsISupports** aPageDescriptor) {
  MOZ_ASSERT(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;

  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() {
  // XXX: We allow this function to be called just once.  If you are going to
  // reset new variables in this function, please make sure the variables will
  // never be re-initialized.  Adding assertions to check |mIsBeingDestroyed|
  // in the setter functions for the variables would be enough.
  if (mIsBeingDestroyed) {
    return NS_ERROR_DOCSHELL_DYING;
  }

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

  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;

  // Brak the cycle with the initial client, if present.
  mInitialClientSource.reset();

  // Make sure we don't record profile timeline markers anymore
  SetRecordProfileTimelineMarkers(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;

  // 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(!mWillChangeProcess);
    mScriptGlobal = nullptr;
  }

  if (GetSessionHistory()) {
    // 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)
    GetSessionHistory()->EvictLocalContentViewers();
  }

  if (mWillChangeProcess) {
    mBrowsingContext->PrepareForProcessChange();
  }

  SetTreeOwner(nullptr);

  mBrowserChild = nullptr;

  mChromeEventHandler = 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() && 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.MoveTo(aX, 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.SetRect(aX, aY, aWidth, 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.
    RefPtr<Document> doc(do_GetInterface(GetAsSupports(mParent)));
    if (doc) {
      doc->FlushPendingNotifications(FlushType::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) {
  PresShell* presShell = GetPresShell();
  NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);

  RefPtr<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) {
  MOZ_ASSERT(!mIsBeingDestroyed);
  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 nsIAppWindow
  return NS_ERROR_NOT_IMPLEMENTED;
}

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

  *aVisibility = false;

  if (!mContentViewer) {
    return NS_OK;
  }

  PresShell* 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->GetInProcessParentDocshell();
  while (parentItem) {
    // Null-check for crash in bug 267804
    if (!parentItem->GetPresShell()) {
      MOZ_ASSERT_UNREACHABLE("parent docshell has null pres shell");
      return NS_OK;
    }

    vm = docShell->GetPresShell()->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->GetInProcessParentDocshell();
  }

  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::SetSuspendMediaWhenInactive(bool aSuspendMediaWhenInactive) {
  mSuspendMediaWhenInactive = aSuspendMediaWhenInactive;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetSuspendMediaWhenInactive(bool* aSuspendMediaWhenInactive) {
  *aSuspendMediaWhenInactive = mSuspendMediaWhenInactive;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetIsActive(bool aIsActive) {
  // Keep track ourselves.
  mBrowsingContext->SetIsActive(aIsActive);

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

  // Tell the window about it
  if (mScriptGlobal) {
    mScriptGlobal->SetIsBackground(!aIsActive);
    if (RefPtr<Document> doc = mScriptGlobal->GetExtantDoc()) {
      // Update orientation when the top-level browsing context becomes active.
      if (aIsActive) {
        if (mBrowsingContext->IsTop()) {
          // We only care about the top-level browsing context.
          uint16_t orientation = mBrowsingContext->GetOrientationLock();
          ScreenOrientation::UpdateActiveOrientationLock(orientation);
        }
      }

      doc->PostVisibilityUpdateEvent();
    }
  }

  // Tell the nsDOMNavigationTiming about it
  RefPtr<nsDOMNavigationTiming> timing = mTiming;
  if (!timing && mContentViewer) {
    Document* 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;
    }

    docshell->SetIsActive(aIsActive);
  }

  // Restart or stop meta refresh timers if necessary
  if (mDisableMetaRefreshWhenInactive) {
    if (mBrowsingContext->GetIsActive()) {
      ResumeRefreshURIs();
    } else {
      SuspendRefreshURIs();
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetIsActive(bool* aIsActive) {
  *aIsActive = mBrowsingContext->GetIsActive();
  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::SetDefaultLoadFlags(uint32_t aDefaultLoadFlags) {
  if (!mWillChangeProcess) {
    mBrowsingContext->SetDefaultLoadFlags(aDefaultLoadFlags);
  } else {
    // Bug 1623565: DevTools tries to clean up defaultLoadFlags on
    // shutdown. Sorry DevTools, your DocShell is in another process.
    NS_WARNING("nsDocShell::SetDefaultLoadFlags called on Zombie DocShell");
  }
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetDefaultLoadFlags(uint32_t* aDefaultLoadFlags) {
  *aDefaultLoadFlags = mBrowsingContext->GetDefaultLoadFlags();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetMixedContentChannel(nsIChannel* aMixedContentChannel) {
#ifdef DEBUG
  // if the channel is non-null
  if (aMixedContentChannel) {
    NS_WARNING_ASSERTION(mBrowsingContext->IsTop(),
                         "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);
  Document* 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::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(nsAString& aTitle) {
  aTitle = mTitle;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetTitle(const nsAString& aTitle) {
  // Avoid unnecessary updates of the title if the URI and the title haven't
  // changed.
  if (mTitleValidForCurrentURI && mTitle == aTitle) {
    return NS_OK;
  }

  // Store local title
  mTitle = aTitle;
  mTitleValidForCurrentURI = true;

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

  if (mCurrentURI && mLoadType != LOAD_ERROR_PAGE) {
    UpdateGlobalHistoryTitle(mCurrentURI);
  }

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

  return NS_OK;
}

nsPoint nsDocShell::GetCurScrollPos() {
  nsPoint scrollPos;
  if (nsIScrollableFrame* sf = GetRootScrollFrame()) {
    scrollPos = sf->GetVisualViewportOffset();
  }
  return scrollPos;
}

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

  ScrollMode scrollMode =
      sf->IsSmoothScroll() ? ScrollMode::SmoothMsd : ScrollMode::Instant;

  nsPoint targetPos(aCurHorizontalPos, aCurVerticalPos);
  sf->ScrollTo(targetPos, scrollMode);

  // Set the visual viewport offset as well.

  RefPtr<PresShell> presShell = GetPresShell();
  NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);

  nsPresContext* presContext = presShell->GetPresContext();
  NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);

  // Only the root content document can have a distinct visual viewport offset.
  if (!presContext->IsRootContentDocument()) {
    return NS_OK;
  }

  // Not on a platform with a distinct visual viewport - don't bother setting
  // the visual viewport offset.
  if (!presShell->IsVisualViewportSizeSet()) {
    return NS_OK;
  }

  presShell->ScrollToVisual(targetPos, layers::FrameMetrics::eMainThread,
                            scrollMode);

  return NS_OK;
}

void nsDocShell::SetScrollbarPreference(mozilla::ScrollbarPreference aPref) {
  if (mScrollbarPref == aPref) {
    return;
  }
  mScrollbarPref = aPref;
  auto* ps = GetPresShell();
  if (!ps) {
    return;
  }
  nsIFrame* scrollFrame = ps->GetRootScrollFrame();
  if (!scrollFrame) {
    return;
  }
  ps->FrameNeedsReflow(scrollFrame, IntrinsicDirty::StyleChange,
                       NS_FRAME_IS_DIRTY);
}

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

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

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

  nsCOMPtr<nsITimerCallback> refreshTimer =
      new nsRefreshTimer(this, aURI, aPrincipal, aDelay, aRepeat, aMetaRefresh);

  BusyFlags busyFlags = GetBusyFlags();

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

  if (busyFlags & BUSY_FLAGS_BUSY ||
      (!mBrowsingContext->GetIsActive() && 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);
  } else {
    // There is no page loading going on right now.  Create the
    // timer and fire it right away.
    nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
    NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);

    nsCOMPtr<nsITimer> timer;
    MOZ_TRY_VAR(timer, NS_NewTimerWithCallback(refreshTimer, aDelay,
                                               nsITimer::TYPE_ONE_SHOT));

    mRefreshURIList->AppendElement(timer);  // owning timer ref
  }
  return NS_OK;
}

nsresult nsDocShell::ForceRefreshURIFromTimer(nsIURI* aURI,
                                              nsIPrincipal* aPrincipal,
                                              int32_t aDelay, bool aMetaRefresh,
                                              nsITimer* aTimer) {
  MOZ_ASSERT(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, aPrincipal, aDelay, aMetaRefresh);
}

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

  RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aURI);
  loadState->SetOriginalURI(mCurrentURI);
  loadState->SetResultPrincipalURI(aURI);
  loadState->SetResultPrincipalURIIsSome(true);
  loadState->SetKeepResultPrincipalURIIfSet(true);

  // Set the triggering pricipal to aPrincipal if available, or current
  // document's principal otherwise.
  nsCOMPtr<nsIPrincipal> principal = aPrincipal;
  RefPtr<Document> doc = GetDocument();
  if (!principal) {
    if (!doc) {
      return NS_ERROR_FAILURE;
    }
    principal = doc->NodePrincipal();
  }
  loadState->SetTriggeringPrincipal(principal);
  if (doc) {
    loadState->SetCsp(doc->GetCsp());
    loadState->SetHasValidUserGestureActivation(
        doc->HasValidTransientUserGestureActivation());
  }

  loadState->SetPrincipalIsExplicit(true);

  /* Check if this META refresh causes a redirection
   * to another site.
   */
  bool equalUri = false;
  nsresult rv = aURI->Equals(mCurrentURI, &equalUri);

  nsCOMPtr<nsIReferrerInfo> referrerInfo;
  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().
     */
    loadState->SetLoadType(LOAD_NORMAL_REPLACE);

    /* For redirects we mimic HTTP, which passes the
     * original referrer.
     * We will pass in referrer but will not send to server
     */
    if (mReferrerInfo) {
      referrerInfo = static_cast<ReferrerInfo*>(mReferrerInfo.get())
                         ->CloneWithNewSendReferrer(false);
    }
  } else {
    loadState->SetLoadType(LOAD_REFRESH);
    /* We do need to pass in a referrer, but we don't want it to
     * be sent to the server.
     * For most refreshes the current URI is an appropriate
     * internal referrer.
     */
    referrerInfo = new ReferrerInfo(mCurrentURI, ReferrerPolicy::_empty, false);
  }

  loadState->SetReferrerInfo(referrerInfo);
  loadState->SetLoadFlags(
      nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL);
  loadState->SetFirstParty(true);

  /*
   * LoadURI(...) will cancel all refresh timers... This causes the
   * Timer and its refreshData instance to be released...
   */
  LoadURI(loadState, false);

  return NS_OK;
}

nsresult nsDocShell::SetupRefreshURIFromHeader(nsIURI* aBaseURI,
                                               nsIPrincipal* aPrincipal,
                                               uint64_t aInnerWindowID,
                                               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,
          aInnerWindowID);

      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, aPrincipal, seconds * 1000, false, true);
      }
    }
  }
  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);

      SetupReferrerInfoFromChannel(aChannel);
      // We have no idea what window id to use for error reporting
      // here, so just pass 0.
      rv = SetupRefreshURIFromHeader(mCurrentURI, principal, 0, 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();

      mRefreshURIList->ReplaceElementAt(callback, i);
    }
  }

  // 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<nsPIDOMWindowOuter> win = GetWindow();
      if (win) {
        nsCOMPtr<nsITimer> timer;
        NS_NewTimerWithCallback(getter_AddRefs(timer), refreshInfo, delay,
                                nsITimer::TYPE_ONE_SHOT);

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

  return NS_OK;
}

nsresult nsDocShell::Embed(nsIContentViewer* aContentViewer,
                           WindowGlobalChild* aWindowActor) {
  // Save the LayoutHistoryState of the previous document, before
  // setting up new document
  PersistLayoutHistoryState();

  nsresult rv = SetupNewViewer(aContentViewer, aWindowActor);
  NS_ENSURE_SUCCESS(rv, rv);

  // 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);

    SetHistoryEntryAndUpdateBC(Nothing(), Some<nsISHEntry*>(mLSHE));
    if (StaticPrefs::fission_sessionHistoryInParent()) {
      mActiveEntry = nullptr;
      mLoadingEntry.swap(mActiveEntry);
      if (XRE_IsParentProcess()) {
        mBrowsingContext->Canonical()->SessionHistoryCommit(mLoadingEntryId);
      } else {
        ContentChild* cc = ContentChild::GetSingleton();
        mozilla::Unused << cc->SendHistoryCommit(mBrowsingContext,
                                                 mLoadingEntryId);
      }
      mLoadingEntryId = 0;
    }
  }

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

//*****************************************************************************
// 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);

    if (this == aProgress) {
      mozilla::Unused << MaybeInitTiming();
      mTiming->NotifyFetchStart(uri,
                                ConvertLoadTypeToNavigationType(mLoadType));
      // If we are starting a DocumentChannel, we need to pass the timing
      // statistics so that should a process switch occur, the starting type can
      // be passed to the new DocShell running in the other content process.
      if (RefPtr<DocumentChannel> docChannel = do_QueryObject(aRequest)) {
        docChannel->SetNavigationTiming(mTiming);
      }
    }

    // Page has begun to load
    mBusyFlags = (BusyFlags)(BUSY_FLAGS_BUSY | BUSY_FLAGS_BEFORE_PAGE_LOAD);

    if ((aStateFlags & STATE_RESTORING) == 0) {
      // Show the progress cursor if the pref is set
      if (StaticPrefs::ui_use_activity_cursor()) {
        nsCOMPtr<nsIWidget> mainWidget;
        GetMainWidget(getter_AddRefs(mainWidget));
        if (mainWidget) {
          mainWidget->SetCursor(eCursor_spinning, nullptr, 0, 0);
        }
      }
    }
  } else if ((~aStateFlags & (STATE_TRANSFERRING | STATE_IS_DOCUMENT)) == 0) {
    // Page is loading
    mBusyFlags = (BusyFlags)(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 (StaticPrefs::ui_use_activity_cursor()) {
      nsCOMPtr<nsIWidget> mainWidget;
      GetMainWidget(getter_AddRefs(mainWidget));
      if (mainWidget) {
        mainWidget->SetCursor(eCursor_standard, nullptr, 0, 0);
      }
    }
  }
  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) {
  if (XRE_IsParentProcess()) {
    // Since we've now changed Documents, notify the BrowsingContext that we've
    // changed. Ideally we'd just let the BrowsingContext do this when it
    // changes the current window global, but that happens before this and we
    // have a lot of tests that depend on the specific ordering of messages.
    if (!(aFlags & nsIWebProgressListener::LOCATION_CHANGE_SAME_DOCUMENT)) {
      GetBrowsingContext()
          ->Canonical()
          ->UpdateSecurityStateForLocationOrMixedContentChange();
    }
  }
  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;
  }

  // DocumentChannel adds redirect chain to global history in the parent
  // process. The redirect chain can't be queried from the content process, so
  // there's no need to update global history here.
  RefPtr<DocumentChannel> docChannel = do_QueryObject(aOldChannel);
  if (!docChannel) {
    // 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 ||
        net::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 {
      // Get the HTTP response code, if available.
      uint32_t responseStatus = 0;
      nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aOldChannel);
      if (httpChannel) {
        Unused << httpChannel->GetResponseStatus(&responseStatus);
      }

      // Add visit N -1 => N
      AddURIVisit(oldURI, 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 && !docChannel) {
    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->GetDocShellContentPrincipal(newURI, this,
                                            getter_AddRefs(principal));
        appCacheChannel->SetChooseApplicationCache(
            NS_ShouldCheckAppCache(principal));
      }
    }
  }

  if (!(aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) &&
      mLoadType & (LOAD_CMD_RELOAD | LOAD_CMD_HISTORY)) {
    mLoadType = LOAD_NORMAL_REPLACE;
    SetHistoryEntryAndUpdateBC(Some(nullptr), Nothing());
  }
}

NS_IMETHODIMP
nsDocShell::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
                           nsresult aStatus, const char16_t* aMessage) {
  MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
                             uint32_t aState) {
  MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
                                   nsIRequest* aRequest, uint32_t aEvent) {
  MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
  return NS_OK;
}

already_AddRefed<nsIURIFixupInfo> nsDocShell::KeywordToURI(
    const nsACString& aKeyword, bool aIsPrivateContext,
    nsIInputStream** aPostData) {
  nsCOMPtr<nsIURIFixupInfo> info;
  if (XRE_IsContentProcess()) {
    dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
    if (!contentChild) {
      return nullptr;
    }

    info = do_CreateInstance("@mozilla.org/docshell/uri-fixup-info;1");
    if (NS_WARN_IF(!info)) {
      return nullptr;
    }
    RefPtr<nsIInputStream> postData;
    RefPtr<nsIURI> uri;
    nsAutoString providerName;
    nsAutoCString keyword(aKeyword);
    // TODO (Bug 1375244): This synchronous IPC messaging should be changed.
    if (contentChild->SendKeywordToURI(keyword, aIsPrivateContext,
                                       &providerName, &postData, &uri)) {
      NS_ConvertUTF8toUTF16 keywordW(aKeyword);
      info->SetKeywordAsSent(keywordW);
      info->SetKeywordProviderName(providerName);
      if (aPostData) {
        postData.forget(aPostData);
      }
      info->SetPreferredURI(uri);
    }
  } else {
    nsCOMPtr<nsIURIFixup> uriFixup = components::URIFixup::Service();
    if (uriFixup) {
      uriFixup->KeywordToURI(aKeyword, aIsPrivateContext, aPostData,
                             getter_AddRefs(info));
    }
  }
  return info.forget();
}

nsresult nsDocShell::EndPageLoad(nsIWebProgress* aProgress,
                                 nsIChannel* aChannel, nsresult aStatus) {
  MOZ_LOG(gDocShellLeakLog, LogLevel::Debug,
          ("DOCSHELL %p EndPageLoad status: %" PRIx32 "\n", this,
           static_cast<uint32_t>(aStatus)));
  if (!aChannel) {
    return NS_ERROR_NULL_POINTER;
  }

  // Make sure to discard the initial client if we never created the initial
  // about:blank document.  Do this before possibly returning from the method
  // due to an error.
  mInitialClientSource.reset();

  nsCOMPtr<nsIConsoleReportCollector> reporter = do_QueryInterface(aChannel);
  if (reporter) {
    nsCOMPtr<nsILoadGroup> loadGroup;
    aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
    if (loadGroup) {
      reporter->FlushConsoleReports(loadGroup);
    } else {
      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);
    }
  }

  // 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;
    nsCOMPtr<nsIContentViewer> contentViewer = mContentViewer;
    contentViewer->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(LOAD_HISTORY);

    // Clear the mLSHE reference to indicate document loading is done one
    // way or another.
    SetHistoryEntryAndUpdateBC(Some(nullptr), Nothing());
  }
  // if there's a refresh header in the channel, this method
  // will set it up for us.
  if (mBrowsingContext->GetIsActive() || !mDisableMetaRefreshWhenInactive)
    RefreshURIFromQueue();

  // Test whether this is the top frame or a subframe
  bool isTopFrame = mBrowsingContext->IsTop();

  //
  // 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. If the www .com additions don't work, try those with an HTTPS scheme
  //      (if appropriate).
  //   5. 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) {
      UnblockEmbedderLoadEventForFailure();
      DisplayLoadError(aStatus, url, nullptr, aChannel);
      return NS_OK;
    }

    // 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 &&
        UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aStatus)) {
      UnblockEmbedderLoadEventForFailure();

      // We don't really need to add the blocked node if we are not testing.
      // This could save a IPC here.
      if (!StaticPrefs::
              privacy_trackingprotection_testing_report_blocked_node()) {
        return NS_OK;
      }

      RefPtr<BrowsingContext> bc = GetBrowsingContext();
      RefPtr<BrowsingContext> parentBC = bc->GetParent();

      if (!parentBC) {
        return NS_OK;
      }

      if (parentBC->IsInProcess()) {
        // We can directly add the blocked node in the parent document if the
        // parent is in the same process.
        nsCOMPtr<nsPIDOMWindowOuter> parentOuter = parentBC->GetDOMWindow();

        if (!parentOuter) {
          return NS_OK;
        }

        nsCOMPtr<nsPIDOMWindowInner> parentInner =
            parentOuter->GetCurrentInnerWindow();

        if (!parentInner) {
          return NS_OK;
        }

        RefPtr<Document> parentDoc;
        parentDoc = parentInner->GetExtantDoc();
        if (!parentDoc) {
          return NS_OK;
        }

        parentDoc->AddBlockedNodeByClassifier(bc->GetEmbedderElement());
      } else {
        // If the parent is out-of-process, we send to the process of the
        // document which embeds the frame to add the blocked node there.
        RefPtr<BrowserChild> browserChild = BrowserChild::GetFrom(this);
        if (browserChild) {
          Unused << browserChild->SendReportBlockedEmbedderNodeByClassifier();
        }
      }

      return NS_OK;
    }

    if ((aStatus == NS_ERROR_UNKNOWN_HOST || aStatus == NS_ERROR_NET_RESET ||
         aStatus == NS_ERROR_CONNECTION_REFUSED) &&
        ((mLoadType == LOAD_NORMAL && isTopFrame) || mAllowKeywordFixup)) {
      //
      // 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) {
        // 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...
        nsAutoCString scheme;
        Unused << url->GetScheme(scheme);
        if (Preferences::GetBool("keyword.enabled", false) &&
            StringBeginsWith(scheme, NS_LITERAL_CSTRING("http"))) {
          bool attemptFixup = false;
          nsAutoCString host;
          Unused << url->GetHost(host);
          if (host.FindChar('.') == kNotFound) {
            attemptFixup = true;
          } else {
            // For domains with dots, we check the public suffix validity.
            nsCOMPtr<nsIEffectiveTLDService> tldService =
                do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
            if (tldService) {
              nsAutoCString suffix;
              attemptFixup =
                  NS_SUCCEEDED(tldService->GetKnownPublicSuffix(url, suffix)) &&
                  suffix.IsEmpty();
            }
          }
          if (attemptFixup) {
            nsCOMPtr<nsIURIFixupInfo> info;
            // only send non-qualified hosts to the keyword server
            if (!mOriginalUriString.IsEmpty()) {
              info = KeywordToURI(mOriginalUriString, UsePrivateBrowsing(),
                                  getter_AddRefs(newPostData));
            } 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))) {
                info = KeywordToURI(utf8Host, UsePrivateBrowsing(),
                                    getter_AddRefs(newPostData));
              } else {
                info = KeywordToURI(host, UsePrivateBrowsing(),
                                    getter_AddRefs(newPostData));
              }
            }
            if (info) {
              info->GetPreferredURI(getter_AddRefs(newURI));
              if (newURI) {
                info->GetKeywordAsSent(keywordAsSent);
                info->GetKeywordProviderName(keywordProviderName);
              }
            }
          }
        }
      }

      //
      // Now try change the address, e.g. turn http://foo into
      // http://www.foo.com, and if that doesn't work try https with
      // https://foo and https://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) {
            nsCOMPtr<nsILoadInfo> info = aChannel->LoadInfo();
            // Skip doing this if our channel was redirected, because we
            // shouldn't be guessing things about the post-redirect URI.
            if (!info->RedirectChain().IsEmpty()) {
              doCreateAlternate = false;
            }
          }
        }
        if (doCreateAlternate) {
          newURI = nullptr;
          newPostData = nullptr;
          keywordProviderName.Truncate();
          keywordAsSent.Truncate();
          nsCOMPtr<nsIURIFixup> uriFixup = components::URIFixup::Service();
          if (uriFixup) {
            uriFixup->CreateFixupURI(
                oldSpec, nsIURIFixup::FIXUP_FLAGS_MAKE_ALTERNATE_URI,
                getter_AddRefs(newPostData), getter_AddRefs(newURI));
          }
        }
      } else if (aStatus == NS_ERROR_CONNECTION_REFUSED &&
                 Preferences::GetBool("browser.fixup.fallback-to-https",
                                      false)) {
        // Try HTTPS, since http didn't work
        if (SchemeIsHTTP(url)) {
          int32_t port = 0;
          url->GetPort(&port);

          // Fall back to HTTPS only if port is default
          if (port == -1) {
            newURI = nullptr;
            newPostData = nullptr;
            Unused << NS_MutateURI(url)
                          .SetScheme(NS_LITERAL_CSTRING("https"))
                          .Finalize(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);

          nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
          MOZ_ASSERT(loadInfo, "loadInfo is required on all channels");
          nsCOMPtr<nsIPrincipal> triggeringPrincipal =
              loadInfo->TriggeringPrincipal();

          // If the new URI is HTTP, it may not work and we may want to fall
          // back to HTTPS, so kick off a speculative connect to get that
          // started.  Even if we don't adjust to HTTPS here, there could be a
          // redirect to HTTPS coming so this could speed things up.
          if (SchemeIsHTTP(url)) {
            int32_t port = 0;
            rv = url->GetPort(&port);

            // only do this if the port is default.
            if (NS_SUCCEEDED(rv) && port == -1) {
              nsCOMPtr<nsIURI> httpsURI;
              rv = NS_MutateURI(url)
                       .SetScheme(NS_LITERAL_CSTRING("https"))
                       .Finalize(getter_AddRefs(httpsURI));

              if (NS_SUCCEEDED(rv)) {
                nsCOMPtr<nsIIOService> ios = do_GetIOService();
                if (ios) {
                  nsCOMPtr<nsISpeculativeConnect> speculativeService =
                      do_QueryInterface(ios);
                  if (speculativeService) {
                    speculativeService->SpeculativeConnect(
                        httpsURI, triggeringPrincipal, nullptr);
                  }
                }
              }
            }
          }

          LoadURIOptions loadURIOptions;
          loadURIOptions.mTriggeringPrincipal = triggeringPrincipal;
          loadURIOptions.mCsp = loadInfo->GetCspToInherit();
          loadURIOptions.mPostData = newPostData;
          return LoadURI(newSpecW, loadURIOptions);
        }
      }
    }

    // Well, fixup didn't work :-(
    // It is time to throw an error dialog box, and be done with it...

    // If we got CONTENT_BLOCKED from EndPageLoad, then we need to fire
    // the error event to our embedder, since tests are relying on this.
    // The error event is usually fired by the caller of InternalLoad, but
    // this particular error can happen asynchronously.
    // Bug 1629201 is filed for having much clearer decision making around
    // which cases need error events.
    bool fireFrameErrorEvent = (aStatus == NS_ERROR_CONTENT_BLOCKED_SHOW_ALT ||
                                aStatus == NS_ERROR_CONTENT_BLOCKED);
    UnblockEmbedderLoadEventForFailure(fireFrameErrorEvent);

    // 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 ||
         aStatus == NS_ERROR_PROXY_FORBIDDEN ||
         aStatus == NS_ERROR_PROXY_NOT_IMPLEMENTED ||
         aStatus == NS_ERROR_PROXY_AUTHENTICATION_FAILED ||
         aStatus == NS_ERROR_PROXY_TOO_MANY_REQUESTS ||
         aStatus == NS_ERROR_MALFORMED_URI ||
         aStatus == NS_ERROR_BLOCKED_BY_POLICY) &&
        (isTopFrame || mUseErrorPages)) {
      DisplayLoadError(aStatus, url, nullptr, aChannel);
    } else if (aStatus == NS_ERROR_NET_TIMEOUT ||
               aStatus == NS_ERROR_PROXY_GATEWAY_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_PROXY_BAD_GATEWAY ||
               aStatus == NS_ERROR_OFFLINE || aStatus == NS_ERROR_MALWARE_URI ||
               aStatus == NS_ERROR_PHISHING_URI ||
               aStatus == NS_ERROR_UNWANTED_URI ||
               aStatus == NS_ERROR_HARMFUL_URI ||
               aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE ||
               aStatus == NS_ERROR_REMOTE_XUL ||
               aStatus == NS_ERROR_INTERCEPTION_FAILED ||
               aStatus == NS_ERROR_NET_INADEQUATE_SECURITY ||
               aStatus == NS_ERROR_NET_HTTP2_SENT_GOAWAY ||
               aStatus == NS_ERROR_NET_HTTP3_PROTOCOL_ERROR ||
               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_UNKNOWN_PROTOCOL) {
      // For unknown protocols we only display an error if the load is triggered
      // by the browser itself, or we're replacing the initial document (and
      // nothing else). Showing the error for page-triggered navigations causes
      // annoying behavior for users, see bug 1528305.
      //
      // We could, maybe, try to detect if this is in response to some user
      // interaction (like clicking a link, or something else) and maybe show
      // the error page in that case. But this allows for ctrl+clicking and such
      // to see the error page.
      nsCOMPtr<nsILoadInfo> info = aChannel->LoadInfo();
      Document* doc = GetDocument();
      if (!info->TriggeringPrincipal()->IsSystemPrincipal() &&
          StaticPrefs::dom_no_unknown_protocol_error_enabled() && doc &&
          !doc->IsInitialDocument()) {
        nsTArray<nsString> params;
        if (NS_FAILED(NS_GetSanitizedURIStringFromURI(
                url, *params.AppendElement()))) {
          params.LastElement().AssignLiteral(u"(unknown uri)");
        }
        nsContentUtils::ReportToConsole(
            nsIScriptError::warningFlag, NS_LITERAL_CSTRING("DOM"), doc,
            nsContentUtils::eDOM_PROPERTIES,
            "UnknownProtocolNavigationPrevented", params);
      } else {
        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
    nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
    PredictorLearnRedirect(url, aChannel, loadInfo->GetOriginAttributes());
  }

  return NS_OK;
}

//*****************************************************************************
// nsDocShell: Content Viewer Management
//*****************************************************************************

nsresult nsDocShell::EnsureContentViewer() {
  if (mContentViewer) {
    return NS_OK;
  }
  if (mIsBeingDestroyed) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIContentSecurityPolicy> cspToInheritForAboutBlank;
  nsCOMPtr<nsIURI> baseURI;
  nsIPrincipal* principal = GetInheritedPrincipal(false);
  nsIPrincipal* storagePrincipal = GetInheritedPrincipal(false, true);

  nsCOMPtr<nsIDocShellTreeItem> parentItem;
  GetInProcessSameTypeParent(getter_AddRefs(parentItem));
  if (parentItem) {
    if (nsCOMPtr<nsPIDOMWindowOuter> domWin = GetWindow()) {
      nsCOMPtr<Element> parentElement = domWin->GetFrameElementInternal();
      if (parentElement) {
        baseURI = parentElement->GetBaseURI();
        cspToInheritForAboutBlank = parentElement->GetCsp();
      }
    }
  }

  nsresult rv = CreateAboutBlankContentViewer(
      principal, storagePrincipal, cspToInheritForAboutBlank, baseURI);

  NS_ENSURE_STATE(mContentViewer);

  if (NS_SUCCEEDED(rv)) {
    RefPtr<Document> doc(GetDocument());
    NS_ASSERTION(doc,
                 "Should have doc if CreateAboutBlankContentViewer "
                 "succeeded!");

    doc->SetIsInitialDocument(true);

    // Documents created using EnsureContentViewer may be transient
    // placeholders created by framescripts before content has a
    // chance to load. In some cases, window.open(..., "noopener")
    // will create such a document and then synchronously tear it
    // down, firing a "pagehide" event. Doing so violates our
    // assertions about DocGroups. It's easier to silence the
    // assertion here than to avoid creating the extra document.
    doc->IgnoreDocGroupMismatches();
  }

  return rv;
}

nsresult nsDocShell::CreateAboutBlankContentViewer(
    nsIPrincipal* aPrincipal, nsIPrincipal* aStoragePrincipal,
    nsIContentSecurityPolicy* aCSP, nsIURI* aBaseURI,
    bool aTryToSaveOldPresentation, bool aCheckPermitUnload,
    WindowGlobalChild* aActor) {
  RefPtr<Document> blankDoc;
  nsCOMPtr<nsIContentViewer> viewer;
  nsresult rv = NS_ERROR_FAILURE;

  MOZ_ASSERT_IF(aActor, aActor->DocumentPrincipal() == aPrincipal);

  /* 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 && !aPrincipal->IsSystemPrincipal() &&
      mItemType != typeChrome) {
    MOZ_ASSERT(aPrincipal->OriginAttributesRef() ==
               mBrowsingContext->OriginAttributesRef());
  }

  // 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;
  bool toBeReset = MaybeInitTiming();
  if (mContentViewer) {
    if (aCheckPermitUnload) {
      // 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.
        MaybeResetInitTiming(toBeReset);
        return NS_ERROR_FAILURE;
      }
      if (mTiming) {
        mTiming->NotifyUnloadAccepted(mCurrentURI);
      }
    }

    mSavingOldViewer = aTryToSaveOldPresentation &&
                       CanSavePresentation(LOAD_NORMAL, nullptr, nullptr);

    // 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);
    // pagehide notification might destroy this docshell.
    if (mIsBeingDestroyed) {
      return NS_ERROR_DOCSHELL_DYING;
    }