dom/base/nsGlobalWindow.cpp
author ffxbld
Sun, 20 Nov 2016 06:13:34 -0800
changeset 323316 b7f895c1dc2e
parent 323245 dcd436e553ed
child 323493 f1a8ad430beb
permissions -rw-r--r--
No bug, Automated blocklist update from host bld-linux64-spot-470 - a=blocklist-update

/* -*- 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 "nsGlobalWindow.h"

#include <algorithm>

#include "mozilla/MemoryReporting.h"

// Local Includes
#include "Navigator.h"
#include "nsContentSecurityManager.h"
#include "nsScreen.h"
#include "nsHistory.h"
#include "nsDOMNavigationTiming.h"
#include "nsIDOMStorageManager.h"
#include "mozilla/dom/DOMStorage.h"
#include "mozilla/dom/IdleRequest.h"
#include "mozilla/dom/Performance.h"
#include "mozilla/dom/StorageEvent.h"
#include "mozilla/dom/StorageEventBinding.h"
#include "mozilla/dom/Timeout.h"
#include "mozilla/IntegerPrintfMacros.h"
#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
#include "mozilla/dom/WindowOrientationObserver.h"
#endif
#include "nsDOMOfflineResourceList.h"
#include "nsError.h"
#include "nsIIdleService.h"
#include "nsISizeOfEventTarget.h"
#include "nsDOMJSUtils.h"
#include "nsArrayUtils.h"
#include "nsIDOMWindowCollection.h"
#include "nsDOMWindowList.h"
#include "mozilla/dom/WakeLock.h"
#include "mozilla/dom/power/PowerManagerService.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIPermissionManager.h"
#include "nsIScriptContext.h"
#include "nsIScriptTimeoutHandler.h"
#include "nsITimeoutHandler.h"
#include "nsIController.h"
#include "nsScriptNameSpaceManager.h"
#include "nsISlowScriptDebug.h"
#include "nsWindowMemoryReporter.h"
#include "WindowNamedPropertiesHandler.h"
#include "nsFrameSelection.h"
#include "nsNetUtil.h"
#include "nsVariant.h"
#include "nsPrintfCString.h"

// Helper Classes
#include "nsJSUtils.h"
#include "jsapi.h"              // for JSAutoRequest
#include "jswrapper.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsReadableUtils.h"
#include "nsDOMClassInfo.h"
#include "nsJSEnvironment.h"
#include "ScriptSettings.h"
#include "mozilla/Preferences.h"
#include "mozilla/Likely.h"
#include "mozilla/Sprintf.h"
#include "mozilla/Unused.h"

// Other Classes
#include "mozilla/dom/BarProps.h"
#include "nsContentCID.h"
#include "nsLayoutStatics.h"
#include "nsCCUncollectableMarker.h"
#include "mozilla/dom/workers/Workers.h"
#include "mozilla/dom/ToJSValue.h"
#include "nsJSPrincipals.h"
#include "mozilla/Attributes.h"
#include "mozilla/Debug.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStates.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/ProcessHangMonitor.h"
#include "mozilla/ThrottledEventQueue.h"
#include "AudioChannelService.h"
#include "nsAboutProtocolUtils.h"
#include "nsCharTraits.h" // NS_IS_HIGH/LOW_SURROGATE
#include "PostMessageEvent.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/TabGroup.h"

// Interfaces Needed
#include "nsIFrame.h"
#include "nsCanvasFrame.h"
#include "nsIWidget.h"
#include "nsIWidgetListener.h"
#include "nsIBaseWindow.h"
#include "nsIDeviceSensors.h"
#include "nsIContent.h"
#include "nsIDocShell.h"
#include "nsIDocCharset.h"
#include "nsIDocument.h"
#include "Crypto.h"
#include "nsIDOMDocument.h"
#include "nsIDOMElement.h"
#include "nsIDOMEvent.h"
#include "nsIDOMOfflineResourceList.h"
#include "nsDOMString.h"
#include "nsIEmbeddingSiteWindow.h"
#include "nsThreadUtils.h"
#include "nsILoadContext.h"
#include "nsIPresShell.h"
#include "nsIScrollableFrame.h"
#include "nsView.h"
#include "nsViewManager.h"
#include "nsISelectionController.h"
#include "nsISelection.h"
#include "nsIPrompt.h"
#include "nsIPromptService.h"
#include "nsIPromptFactory.h"
#include "nsIWritablePropertyBag2.h"
#include "nsIWebNavigation.h"
#include "nsIWebBrowserChrome.h"
#include "nsIWebBrowserFind.h"  // For window.find()
#include "nsIWindowMediator.h"  // For window.find()
#include "nsComputedDOMStyle.h"
#include "nsDOMCID.h"
#include "nsDOMWindowUtils.h"
#include "nsIWindowWatcher.h"
#include "nsPIWindowWatcher.h"
#include "nsIContentViewer.h"
#include "nsIScriptError.h"
#include "nsIControllers.h"
#include "nsIControllerContext.h"
#include "nsGlobalWindowCommands.h"
#include "nsQueryObject.h"
#include "nsContentUtils.h"
#include "nsCSSProps.h"
#include "nsIDOMFileList.h"
#include "nsIURIFixup.h"
#ifndef DEBUG
#include "nsIAppStartup.h"
#include "nsToolkitCompsCID.h"
#endif
#include "nsCDefaultURIFixup.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventStateManager.h"
#include "nsIObserverService.h"
#include "nsFocusManager.h"
#include "nsIXULWindow.h"
#include "nsITimedChannel.h"
#include "nsServiceManagerUtils.h"
#ifdef MOZ_XUL
#include "nsIDOMXULControlElement.h"
#include "nsMenuPopupFrame.h"
#endif
#include "mozilla/dom/CustomEvent.h"
#include "nsIJARChannel.h"
#include "nsIScreenManager.h"
#include "nsIEffectiveTLDService.h"

#include "xpcprivate.h"

#ifdef NS_PRINTING
#include "nsIPrintSettings.h"
#include "nsIPrintSettingsService.h"
#include "nsIWebBrowserPrint.h"
#endif

#include "nsWindowRoot.h"
#include "nsNetCID.h"
#include "nsIArray.h"

// XXX An unfortunate dependency exists here (two XUL files).
#include "nsIDOMXULDocument.h"
#include "nsIDOMXULCommandDispatcher.h"

#include "nsBindingManager.h"
#include "nsXBLService.h"

// used for popup blocking, needs to be converted to something
// belonging to the back-end like nsIContentPolicy
#include "nsIPopupWindowManager.h"

#include "nsIDragService.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Selection.h"
#include "nsFrameLoader.h"
#include "nsISupportsPrimitives.h"
#include "nsXPCOMCID.h"
#include "mozilla/Logging.h"
#include "prenv.h"
#include "prprf.h"

#include "mozilla/dom/IDBFactory.h"
#include "mozilla/dom/MessageChannel.h"
#include "mozilla/dom/Promise.h"

#ifdef MOZ_GAMEPAD
#include "mozilla/dom/Gamepad.h"
#include "mozilla/dom/GamepadManager.h"
#endif

#include "mozilla/dom/VRDisplay.h"
#include "mozilla/dom/VREventObserver.h"

#include "nsRefreshDriver.h"
#include "Layers.h"

#include "mozilla/AddonPathService.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/Services.h"
#include "mozilla/Telemetry.h"
#include "mozilla/dom/Location.h"
#include "nsHTMLDocument.h"
#include "nsWrapperCacheInlines.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "prrng.h"
#include "nsSandboxFlags.h"
#include "TimeChangeObserver.h"
#include "mozilla/dom/AudioContext.h"
#include "mozilla/dom/BrowserElementDictionariesBinding.h"
#include "mozilla/dom/cache/CacheStorage.h"
#include "mozilla/dom/Console.h"
#include "mozilla/dom/Fetch.h"
#include "mozilla/dom/FunctionBinding.h"
#include "mozilla/dom/HashChangeEvent.h"
#include "mozilla/dom/MozSelfSupportBinding.h"
#include "mozilla/dom/PopStateEvent.h"
#include "mozilla/dom/PopupBlockedEvent.h"
#include "mozilla/dom/PrimitiveConversions.h"
#include "mozilla/dom/WindowBinding.h"
#include "nsITabChild.h"
#include "mozilla/dom/MediaQueryList.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/NavigatorBinding.h"
#include "mozilla/dom/ImageBitmap.h"
#include "mozilla/dom/ImageBitmapBinding.h"
#include "mozilla/dom/ServiceWorkerRegistration.h"
#include "mozilla/dom/U2F.h"
#include "mozilla/dom/WebIDLGlobalNameHash.h"
#include "mozilla/dom/Worklet.h"
#ifdef HAVE_SIDEBAR
#include "mozilla/dom/ExternalBinding.h"
#endif

#ifdef MOZ_WEBSPEECH
#include "mozilla/dom/SpeechSynthesis.h"
#endif

#ifdef MOZ_B2G
#include "nsPISocketTransportService.h"
#endif

// Apple system headers seem to have a check() macro.  <sigh>
#ifdef check
class nsIScriptTimeoutHandler;
#undef check
#endif // check
#include "AccessCheck.h"

#ifdef ANDROID
#include <android/log.h>
#endif

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

static const char kStorageEnabled[] = "dom.storage.enabled";

using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::dom::ipc;
using mozilla::BasePrincipal;
using mozilla::PrincipalOriginAttributes;
using mozilla::TimeStamp;
using mozilla::TimeDuration;
using mozilla::dom::cache::CacheStorage;

static LazyLogModule gDOMLeakPRLog("DOMLeak");

nsGlobalWindow::WindowByIdTable *nsGlobalWindow::sWindowsById = nullptr;
bool nsGlobalWindow::sWarnedAboutWindowInternal = false;
bool nsGlobalWindow::sIdleObserversAPIFuzzTimeDisabled = false;

static int32_t              gRefCnt                    = 0;
static int32_t              gOpenPopupSpamCount        = 0;
static PopupControlState    gPopupControlState         = openAbused;
static int32_t              gRunningTimeoutDepth       = 0;
static bool                 gMouseDown                 = false;
static bool                 gDragServiceDisabled       = false;
static FILE                *gDumpFile                  = nullptr;
static uint32_t             gSerialCounter             = 0;
static TimeStamp            gLastRecordedRecentTimeouts;
#define STATISTICS_INTERVAL (30 * PR_MSEC_PER_SEC)

#ifdef DEBUG_jst
int32_t gTimeoutCnt                                    = 0;
#endif

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

#define DOM_TOUCH_LISTENER_ADDED "dom-touch-listener-added"

// The default shortest interval/timeout we permit
#define DEFAULT_MIN_TIMEOUT_VALUE 4 // 4ms
#define DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE 1000 // 1000ms
static int32_t gMinTimeoutValue;
static int32_t gMinBackgroundTimeoutValue;
inline int32_t
nsGlobalWindow::DOMMinTimeoutValue() const {
  // Don't use the background timeout value when there are audio contexts
  // present, so that baackground audio can keep running smoothly. (bug 1181073)
  bool isBackground = mAudioContexts.IsEmpty() &&
    (!mOuterWindow || mOuterWindow->IsBackground());
  return
    std::max(isBackground ? gMinBackgroundTimeoutValue : gMinTimeoutValue, 0);
}

// The number of nested timeouts before we start clamping. HTML5 says 1, WebKit
// uses 5.
#define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5

// The longest interval (as PRIntervalTime) we permit, or that our
// timer code can handle, really. See DELAY_INTERVAL_LIMIT in
// nsTimerImpl.h for details.
#define DOM_MAX_TIMEOUT_VALUE    DELAY_INTERVAL_LIMIT

// The interval at which we execute idle callbacks
static uint32_t gThrottledIdlePeriodLength;

#define DEFAULT_THROTTLED_IDLE_PERIOD_LENGTH 10000

#define FORWARD_TO_OUTER(method, args, err_rval)                              \
  PR_BEGIN_MACRO                                                              \
  if (IsInnerWindow()) {                                                      \
    nsGlobalWindow *outer = GetOuterWindowInternal();                         \
    if (!AsInner()->HasActiveDocument()) {                                    \
      NS_WARNING(outer ?                                                      \
                 "Inner window does not have active document." :              \
                 "No outer window available!");                               \
      return err_rval;                                                        \
    }                                                                         \
    return outer->method args;                                                \
  }                                                                           \
  PR_END_MACRO

#define FORWARD_TO_OUTER_OR_THROW(method, args, errorresult, err_rval)        \
  PR_BEGIN_MACRO                                                              \
  MOZ_RELEASE_ASSERT(IsInnerWindow());                                        \
  nsGlobalWindow *outer = GetOuterWindowInternal();                           \
  if (MOZ_LIKELY(AsInner()->HasActiveDocument())) {                           \
    return outer->method args;                                                \
  }                                                                           \
  if (!outer) {                                                               \
    NS_WARNING("No outer window available!");                                 \
    errorresult.Throw(NS_ERROR_NOT_INITIALIZED);                              \
  } else {                                                                    \
    errorresult.Throw(NS_ERROR_XPC_SECURITY_MANAGER_VETO);                    \
  }                                                                           \
  return err_rval;                                                            \
  PR_END_MACRO

#define FORWARD_TO_OUTER_VOID(method, args)                                   \
  PR_BEGIN_MACRO                                                              \
  if (IsInnerWindow()) {                                                      \
    nsGlobalWindow *outer = GetOuterWindowInternal();                         \
    if (!AsInner()->HasActiveDocument()) {                                    \
      NS_WARNING(outer ?                                                      \
                 "Inner window does not have active document." :              \
                 "No outer window available!");                               \
      return;                                                                 \
    }                                                                         \
    outer->method args;                                                       \
    return;                                                                   \
  }                                                                           \
  PR_END_MACRO

#define FORWARD_TO_OUTER_CHROME(method, args, err_rval)                       \
  PR_BEGIN_MACRO                                                              \
  if (IsInnerWindow()) {                                                      \
    nsGlobalWindow *outer = GetOuterWindowInternal();                         \
    if (!AsInner()->HasActiveDocument()) {                                    \
      NS_WARNING(outer ?                                                      \
                 "Inner window does not have active document." :              \
                 "No outer window available!");                               \
      return err_rval;                                                        \
    }                                                                         \
    return ((nsGlobalChromeWindow *)outer)->method args;                      \
  }                                                                           \
  PR_END_MACRO

#define FORWARD_TO_INNER_CHROME(method, args, err_rval)                       \
  PR_BEGIN_MACRO                                                              \
  if (IsOuterWindow()) {                                                      \
    if (!mInnerWindow) {                                                      \
      NS_WARNING("No inner window available!");                               \
      return err_rval;                                                        \
    }                                                                         \
    return ((nsGlobalChromeWindow *)nsGlobalWindow::Cast(mInnerWindow))->method args; \
  }                                                                           \
  PR_END_MACRO

#define FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(method, args, err_rval)         \
  PR_BEGIN_MACRO                                                              \
  if (IsInnerWindow()) {                                                      \
    nsGlobalWindow *outer = GetOuterWindowInternal();                         \
    if (!AsInner()->HasActiveDocument()) {                                    \
      NS_WARNING(outer ?                                                      \
                 "Inner window does not have active document." :              \
                 "No outer window available!");                               \
      return err_rval;                                                        \
    }                                                                         \
    return ((nsGlobalModalWindow *)outer)->method args;                       \
  }                                                                           \
  PR_END_MACRO

#define FORWARD_TO_INNER(method, args, err_rval)                              \
  PR_BEGIN_MACRO                                                              \
  if (IsOuterWindow()) {                                                      \
    if (!mInnerWindow) {                                                      \
      NS_WARNING("No inner window available!");                               \
      return err_rval;                                                        \
    }                                                                         \
    return GetCurrentInnerWindowInternal()->method args;                      \
  }                                                                           \
  PR_END_MACRO

#define FORWARD_TO_INNER_MODAL_CONTENT_WINDOW(method, args, err_rval)         \
  PR_BEGIN_MACRO                                                              \
  if (IsOuterWindow()) {                                                      \
    if (!mInnerWindow) {                                                      \
      NS_WARNING("No inner window available!");                               \
      return err_rval;                                                        \
    }                                                                         \
    return ((nsGlobalModalWindow*)GetCurrentInnerWindowInternal())->method args; \
  }                                                                           \
  PR_END_MACRO

#define FORWARD_TO_INNER_VOID(method, args)                                   \
  PR_BEGIN_MACRO                                                              \
  if (IsOuterWindow()) {                                                      \
    if (!mInnerWindow) {                                                      \
      NS_WARNING("No inner window available!");                               \
      return;                                                                 \
    }                                                                         \
    GetCurrentInnerWindowInternal()->method args;                             \
    return;                                                                   \
  }                                                                           \
  PR_END_MACRO

// Same as FORWARD_TO_INNER, but this will create a fresh inner if an
// inner doesn't already exists.
#define FORWARD_TO_INNER_CREATE(method, args, err_rval)                       \
  PR_BEGIN_MACRO                                                              \
  if (IsOuterWindow()) {                                                      \
    if (!mInnerWindow) {                                                      \
      if (mIsClosed) {                                                        \
        return err_rval;                                                      \
      }                                                                       \
      nsCOMPtr<nsIDocument> kungFuDeathGrip = GetDoc();                       \
      ::mozilla::Unused << kungFuDeathGrip;                                   \
      if (!mInnerWindow) {                                                    \
        return err_rval;                                                      \
      }                                                                       \
    }                                                                         \
    return GetCurrentInnerWindowInternal()->method args;                      \
  }                                                                           \
  PR_END_MACRO

// CIDs
static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID);

#define NETWORK_UPLOAD_EVENT_NAME     NS_LITERAL_STRING("moznetworkupload")
#define NETWORK_DOWNLOAD_EVENT_NAME   NS_LITERAL_STRING("moznetworkdownload")

/**
 * An indirect observer object that means we don't have to implement nsIObserver
 * on nsGlobalWindow, where any script could see it.
 */
class nsGlobalWindowObserver final : public nsIObserver,
                                     public nsIInterfaceRequestor
{
public:
  explicit nsGlobalWindowObserver(nsGlobalWindow* aWindow) : mWindow(aWindow) {}
  NS_DECL_ISUPPORTS
  NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) override
  {
    if (!mWindow)
      return NS_OK;
    return mWindow->Observe(aSubject, aTopic, aData);
  }
  void Forget() { mWindow = nullptr; }
  NS_IMETHOD GetInterface(const nsIID& aIID, void** aResult) override
  {
    if (mWindow && aIID.Equals(NS_GET_IID(nsIDOMWindow)) && mWindow) {
      return mWindow->QueryInterface(aIID, aResult);
    }
    return NS_NOINTERFACE;
  }

private:
  ~nsGlobalWindowObserver() = default;

  // This reference is non-owning and safe because it's cleared by
  // nsGlobalWindow::CleanUp().
  nsGlobalWindow* MOZ_NON_OWNING_REF mWindow;
};

NS_IMPL_ISUPPORTS(nsGlobalWindowObserver, nsIObserver, nsIInterfaceRequestor)

static already_AddRefed<nsIVariant>
CreateVoidVariant()
{
  RefPtr<nsVariantCC> writable = new nsVariantCC();
  writable->SetAsVoid();
  return writable.forget();
}

nsresult
DialogValueHolder::Get(nsIPrincipal* aSubject, nsIVariant** aResult)
{
  nsCOMPtr<nsIVariant> result;
  if (aSubject->SubsumesConsideringDomain(mOrigin)) {
    result = mValue;
  } else {
    result = CreateVoidVariant();
  }
  result.forget(aResult);
  return NS_OK;
}

void
DialogValueHolder::Get(JSContext* aCx, JS::Handle<JSObject*> aScope,
                       nsIPrincipal* aSubject,
                       JS::MutableHandle<JS::Value> aResult,
                       mozilla::ErrorResult& aError)
{
  if (aSubject->Subsumes(mOrigin)) {
    aError = nsContentUtils::XPConnect()->VariantToJS(aCx, aScope,
                                                      mValue, aResult);
  } else {
    aResult.setUndefined();
  }
}

void
nsGlobalWindow::PostThrottledIdleCallback()
{
  AssertIsOnMainThread();

  if (mThrottledIdleRequestCallbacks.isEmpty())
    return;

  RefPtr<IdleRequest> request(mThrottledIdleRequestCallbacks.popFirst());
  // ownership transferred from mThrottledIdleRequestCallbacks to
  // mIdleRequestCallbacks
  mIdleRequestCallbacks.insertBack(request);
  NS_IdleDispatchToCurrentThread(request.forget());
}

/* static */ void
nsGlobalWindow::InsertIdleCallbackIntoList(IdleRequest* aRequest,
                                           IdleRequests& aList)
{
  aList.insertBack(aRequest);
  aRequest->AddRef();
}

uint32_t
nsGlobalWindow::RequestIdleCallback(JSContext* aCx,
                                    IdleRequestCallback& aCallback,
                                    const IdleRequestOptions& aOptions,
                                    ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());
  AssertIsOnMainThread();

  uint32_t handle = ++mIdleRequestCallbackCounter;

  RefPtr<IdleRequest> request =
    new IdleRequest(aCx, AsInner(), aCallback, handle);

  if (aOptions.mTimeout.WasPassed()) {
    aError = request->SetTimeout(aOptions.mTimeout.Value());
    if (NS_WARN_IF(aError.Failed())) {
      return 0;
    }
  }

  nsGlobalWindow* outer = GetOuterWindowInternal();
  if (outer && outer->AsOuter()->IsBackground()) {
    // mThrottledIdleRequestCallbacks now owns request
    InsertIdleCallbackIntoList(request, mThrottledIdleRequestCallbacks);

    NS_DelayedDispatchToCurrentThread(
      NewRunnableMethod(this, &nsGlobalWindow::PostThrottledIdleCallback),
      10000);
  } else {
    MOZ_ASSERT(mThrottledIdleRequestCallbacks.isEmpty());

    // mIdleRequestCallbacks now owns request
    InsertIdleCallbackIntoList(request, mIdleRequestCallbacks);

    NS_IdleDispatchToCurrentThread(request.forget());
  }

  return handle;
}

void
nsGlobalWindow::CancelIdleCallback(uint32_t aHandle)
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());

  for (IdleRequest* r : mIdleRequestCallbacks) {
    if (r->Handle() == aHandle) {
      r->Cancel();
      break;
    }
  }
}

void
nsGlobalWindow::DisableIdleCallbackRequests()
{
  while (!mIdleRequestCallbacks.isEmpty()) {
    RefPtr<IdleRequest> request = mIdleRequestCallbacks.popFirst();
    request->Cancel();
  }

  while (!mThrottledIdleRequestCallbacks.isEmpty()) {
    RefPtr<IdleRequest> request = mThrottledIdleRequestCallbacks.popFirst();
    request->Cancel();
  }
}

void nsGlobalWindow::UnthrottleIdleCallbackRequests()
{
  AssertIsOnMainThread();

  while (!mThrottledIdleRequestCallbacks.isEmpty()) {
    RefPtr<IdleRequest> request(mThrottledIdleRequestCallbacks.popFirst());
    mIdleRequestCallbacks.insertBack(request);
    NS_IdleDispatchToCurrentThread(request.forget());
  }
}


namespace mozilla {
namespace dom {
extern uint64_t
NextWindowID();
} // namespace dom
} // namespace mozilla

template<class T>
nsPIDOMWindow<T>::nsPIDOMWindow(nsPIDOMWindowOuter *aOuterWindow)
: mFrameElement(nullptr), mDocShell(nullptr), mModalStateDepth(0),
  mRunningTimeout(nullptr), mMutationBits(0), mIsDocumentLoaded(false),
  mIsHandlingResizeEvent(false), mIsInnerWindow(aOuterWindow != nullptr),
  mMayHavePaintEventListener(false), mMayHaveTouchEventListener(false),
  mMayHaveMouseEnterLeaveEventListener(false),
  mMayHavePointerEnterLeaveEventListener(false),
  mInnerObjectsFreed(false),
  mIsModalContentWindow(false),
  mIsActive(false), mIsBackground(false),
  mMediaSuspend(Preferences::GetBool("media.block-autoplay-until-in-foreground", true) ?
    nsISuspendedTypes::SUSPENDED_BLOCK : nsISuspendedTypes::NONE_SUSPENDED),
  mAudioMuted(false), mAudioVolume(1.0), mAudioCaptured(false),
  mDesktopModeViewport(false), mIsRootOuterWindow(false), mInnerWindow(nullptr),
  mOuterWindow(aOuterWindow),
  // Make sure no actual window ends up with mWindowID == 0
  mWindowID(NextWindowID()), mHasNotifiedGlobalCreated(false),
  mMarkedCCGeneration(0), mServiceWorkersTestingEnabled(false)
 {}

template<class T>
nsPIDOMWindow<T>::~nsPIDOMWindow() {}

/* static */
nsPIDOMWindowOuter*
nsPIDOMWindowOuter::GetFromCurrentInner(nsPIDOMWindowInner* aInner)
{
  if (!aInner) {
    return nullptr;
  }

  nsPIDOMWindowOuter* outer = aInner->GetOuterWindow();
  if (!outer || outer->GetCurrentInnerWindow() != aInner) {
    return nullptr;
  }

  return outer;
}

// DialogValueHolder CC goop.
NS_IMPL_CYCLE_COLLECTION(DialogValueHolder, mValue)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DialogValueHolder)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(DialogValueHolder)
NS_IMPL_CYCLE_COLLECTING_RELEASE(DialogValueHolder)

//*****************************************************************************
// nsOuterWindowProxy: Outer Window Proxy
//*****************************************************************************

class nsOuterWindowProxy : public js::Wrapper
{
public:
  constexpr nsOuterWindowProxy() : js::Wrapper(0) { }

  bool finalizeInBackground(const JS::Value& priv) const override {
    return false;
  }

  // Standard internal methods
  bool getOwnPropertyDescriptor(JSContext* cx,
                                JS::Handle<JSObject*> proxy,
                                JS::Handle<jsid> id,
                                JS::MutableHandle<JS::PropertyDescriptor> desc)
                                const override;
  bool defineProperty(JSContext* cx,
                      JS::Handle<JSObject*> proxy,
                      JS::Handle<jsid> id,
                      JS::Handle<JS::PropertyDescriptor> desc,
                      JS::ObjectOpResult &result) const override;
  bool ownPropertyKeys(JSContext *cx,
                       JS::Handle<JSObject*> proxy,
                       JS::AutoIdVector &props) const override;
  bool delete_(JSContext *cx, JS::Handle<JSObject*> proxy,
                       JS::Handle<jsid> id,
                       JS::ObjectOpResult &result) const override;

  bool getPrototypeIfOrdinary(JSContext* cx,
                              JS::Handle<JSObject*> proxy,
                              bool* isOrdinary,
                              JS::MutableHandle<JSObject*> protop) const override;

  bool enumerate(JSContext *cx, JS::Handle<JSObject*> proxy,
                 JS::MutableHandle<JSObject*> vp) const override;
  bool preventExtensions(JSContext* cx,
                         JS::Handle<JSObject*> proxy,
                         JS::ObjectOpResult& result) const override;
  bool isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, bool *extensible)
                    const override;
  bool has(JSContext *cx, JS::Handle<JSObject*> proxy,
           JS::Handle<jsid> id, bool *bp) const override;
  bool get(JSContext *cx, JS::Handle<JSObject*> proxy,
           JS::Handle<JS::Value> receiver,
           JS::Handle<jsid> id,
           JS::MutableHandle<JS::Value> vp) const override;
  bool set(JSContext *cx, JS::Handle<JSObject*> proxy,
           JS::Handle<jsid> id, JS::Handle<JS::Value> v,
           JS::Handle<JS::Value> receiver,
           JS::ObjectOpResult &result) const override;

  // SpiderMonkey extensions
  bool getPropertyDescriptor(JSContext* cx,
                             JS::Handle<JSObject*> proxy,
                             JS::Handle<jsid> id,
                             JS::MutableHandle<JS::PropertyDescriptor> desc)
                             const override;
  bool hasOwn(JSContext *cx, JS::Handle<JSObject*> proxy,
              JS::Handle<jsid> id, bool *bp) const override;
  bool getOwnEnumerablePropertyKeys(JSContext *cx, JS::Handle<JSObject*> proxy,
                                    JS::AutoIdVector &props) const override;
  const char *className(JSContext *cx,
                        JS::Handle<JSObject*> wrapper) const override;

  void finalize(JSFreeOp *fop, JSObject *proxy) const override;

  bool isCallable(JSObject *obj) const override {
    return false;
  }
  bool isConstructor(JSObject *obj) const override {
    return false;
  }

  bool watch(JSContext *cx, JS::Handle<JSObject*> proxy,
             JS::Handle<jsid> id, JS::Handle<JSObject*> callable) const override;
  bool unwatch(JSContext *cx, JS::Handle<JSObject*> proxy,
               JS::Handle<jsid> id) const override;

  static void ObjectMoved(JSObject *obj, const JSObject *old);

  static const nsOuterWindowProxy singleton;

protected:
  static nsGlobalWindow* GetOuterWindow(JSObject *proxy)
  {
    nsGlobalWindow* outerWindow = nsGlobalWindow::FromSupports(
      static_cast<nsISupports*>(js::GetProxyExtra(proxy, 0).toPrivate()));
    MOZ_ASSERT_IF(outerWindow, outerWindow->IsOuterWindow());
    return outerWindow;
  }

  // False return value means we threw an exception.  True return value
  // but false "found" means we didn't have a subframe at that index.
  bool GetSubframeWindow(JSContext *cx, JS::Handle<JSObject*> proxy,
                         JS::Handle<jsid> id,
                         JS::MutableHandle<JS::Value> vp,
                         bool &found) const;

  // Returns a non-null window only if id is an index and we have a
  // window at that index.
  already_AddRefed<nsPIDOMWindowOuter>
  GetSubframeWindow(JSContext *cx,
                    JS::Handle<JSObject*> proxy,
                    JS::Handle<jsid> id) const;

  bool AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy,
                                  JS::AutoIdVector &props) const;
};

static const js::ClassExtension OuterWindowProxyClassExtension = PROXY_MAKE_EXT(
    nsOuterWindowProxy::ObjectMoved
);

const js::Class OuterWindowProxyClass = PROXY_CLASS_WITH_EXT(
    "Proxy",
    0, /* additional class flags */
    &OuterWindowProxyClassExtension);

const char *
nsOuterWindowProxy::className(JSContext *cx, JS::Handle<JSObject*> proxy) const
{
    MOZ_ASSERT(js::IsProxy(proxy));

    return "Window";
}

void
nsOuterWindowProxy::finalize(JSFreeOp *fop, JSObject *proxy) const
{
  nsGlobalWindow* outerWindow = GetOuterWindow(proxy);
  if (outerWindow) {
    outerWindow->ClearWrapper();

    // Ideally we would use OnFinalize here, but it's possible that
    // EnsureScriptEnvironment will later be called on the window, and we don't
    // want to create a new script object in that case. Therefore, we need to
    // write a non-null value that will reliably crash when dereferenced.
    outerWindow->PoisonOuterWindowProxy(proxy);
  }
}

bool
nsOuterWindowProxy::getPropertyDescriptor(JSContext* cx,
                                          JS::Handle<JSObject*> proxy,
                                          JS::Handle<jsid> id,
                                          JS::MutableHandle<JS::PropertyDescriptor> desc) const
{
  // The only thing we can do differently from js::Wrapper is shadow stuff with
  // our indexed properties, so we can just try getOwnPropertyDescriptor and if
  // that gives us nothing call on through to js::Wrapper.
  desc.object().set(nullptr);
  if (!getOwnPropertyDescriptor(cx, proxy, id, desc)) {
    return false;
  }

  if (desc.object()) {
    return true;
  }

  return js::Wrapper::getPropertyDescriptor(cx, proxy, id, desc);
}

bool
nsOuterWindowProxy::getOwnPropertyDescriptor(JSContext* cx,
                                             JS::Handle<JSObject*> proxy,
                                             JS::Handle<jsid> id,
                                             JS::MutableHandle<JS::PropertyDescriptor> desc)
                                             const
{
  bool found;
  if (!GetSubframeWindow(cx, proxy, id, desc.value(), found)) {
    return false;
  }
  if (found) {
    FillPropertyDescriptor(desc, proxy, true);
    return true;
  }
  // else fall through to js::Wrapper

  // When we change this to always claim the property is configurable (bug
  // 1178639), update the comments in nsOuterWindowProxy::defineProperty
  // accordingly.
  return js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc);
}

bool
nsOuterWindowProxy::defineProperty(JSContext* cx,
                                   JS::Handle<JSObject*> proxy,
                                   JS::Handle<jsid> id,
                                   JS::Handle<JS::PropertyDescriptor> desc,
                                   JS::ObjectOpResult &result) const
{
  if (IsArrayIndex(GetArrayIndexFromId(cx, id))) {
    // Spec says to Reject whether this is a supported index or not,
    // since we have no indexed setter or indexed creator.  It is up
    // to the caller to decide whether to throw a TypeError.
    return result.failCantDefineWindowElement();
  }

#ifndef RELEASE_OR_BETA // To be turned on in bug 1178638.
  // For now, allow chrome code to define non-configurable properties
  // on windows, until we sort out what exactly the addon SDK is
  // doing.  In the meantime, this still allows us to test web compat
  // behavior.
  if (desc.hasConfigurable() && !desc.configurable() &&
      !nsContentUtils::IsCallerChrome()) {
    return ThrowErrorMessage(cx, MSG_DEFINE_NON_CONFIGURABLE_PROP_ON_WINDOW);
  }

  // Note that if hasConfigurable() is false we do NOT want to
  // setConfigurable(true).  That would make this code:
  //
  //   var x;
  //   window.x = 5;
  //
  // fail, because the JS engine ends up converting the assignment into a define
  // with !hasConfigurable(), but the var actually declared a non-configurable
  // property on our underlying Window object, so the set would fail if we
  // forced setConfigurable(true) here.  What we want to do instead is change
  // getOwnPropertyDescriptor to always claim configurable.  See bug 1178639.
#endif

  return js::Wrapper::defineProperty(cx, proxy, id, desc, result);
}

bool
nsOuterWindowProxy::ownPropertyKeys(JSContext *cx,
                                    JS::Handle<JSObject*> proxy,
                                    JS::AutoIdVector &props) const
{
  // Just our indexed stuff followed by our "normal" own property names.
  if (!AppendIndexedPropertyNames(cx, proxy, props)) {
    return false;
  }

  JS::AutoIdVector innerProps(cx);
  if (!js::Wrapper::ownPropertyKeys(cx, proxy, innerProps)) {
    return false;
  }
  return js::AppendUnique(cx, props, innerProps);
}

bool
nsOuterWindowProxy::delete_(JSContext *cx, JS::Handle<JSObject*> proxy,
                            JS::Handle<jsid> id, JS::ObjectOpResult &result) const
{
  if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
    // Fail (which means throw if strict, else return false).
    return result.failCantDeleteWindowElement();
  }

  if (IsArrayIndex(GetArrayIndexFromId(cx, id))) {
    // Indexed, but not supported.  Spec says return true.
    return result.succeed();
  }

  return js::Wrapper::delete_(cx, proxy, id, result);
}

bool
nsOuterWindowProxy::getPrototypeIfOrdinary(JSContext* cx,
                                           JS::Handle<JSObject*> proxy,
                                           bool* isOrdinary,
                                           JS::MutableHandle<JSObject*> protop) const
{
  // Window's [[GetPrototypeOf]] trap isn't the ordinary definition:
  //
  //   https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-getprototypeof
  //
  // We nonetheless can implement it with a static [[Prototype]], because
  // wrapper-class handlers (particularly, XOW in FilteringWrapper.cpp) supply
  // all non-ordinary behavior.
  //
  // But from a spec point of view, it's the exact same object in both cases --
  // only the observer's changed.  So this getPrototypeIfOrdinary trap on the
  // non-wrapper object *must* report non-ordinary, even if static [[Prototype]]
  // usually means ordinary.
  *isOrdinary = false;
  return true;
}

bool
nsOuterWindowProxy::preventExtensions(JSContext* cx,
                                      JS::Handle<JSObject*> proxy,
                                      JS::ObjectOpResult& result) const
{
  // If [[Extensible]] could be false, then navigating a window could navigate
  // to a window that's [[Extensible]] after being at one that wasn't: an
  // invariant violation.  So never change a window's extensibility.
  return result.failCantPreventExtensions();
}

bool
nsOuterWindowProxy::isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy,
                                 bool *extensible) const
{
  // See above.
  *extensible = true;
  return true;
}

bool
nsOuterWindowProxy::has(JSContext *cx, JS::Handle<JSObject*> proxy,
                        JS::Handle<jsid> id, bool *bp) const
{
  if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
    *bp = true;
    return true;
  }

  return js::Wrapper::has(cx, proxy, id, bp);
}

bool
nsOuterWindowProxy::hasOwn(JSContext *cx, JS::Handle<JSObject*> proxy,
                           JS::Handle<jsid> id, bool *bp) const
{
  if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
    *bp = true;
    return true;
  }

  return js::Wrapper::hasOwn(cx, proxy, id, bp);
}

bool
nsOuterWindowProxy::get(JSContext *cx, JS::Handle<JSObject*> proxy,
                        JS::Handle<JS::Value> receiver,
                        JS::Handle<jsid> id,
                        JS::MutableHandle<JS::Value> vp) const
{
  if (id == nsDOMClassInfo::sWrappedJSObject_id &&
      xpc::AccessCheck::isChrome(js::GetContextCompartment(cx))) {
    vp.set(JS::ObjectValue(*proxy));
    return true;
  }

  bool found;
  if (!GetSubframeWindow(cx, proxy, id, vp, found)) {
    return false;
  }
  if (found) {
    return true;
  }
  // Else fall through to js::Wrapper

  return js::Wrapper::get(cx, proxy, receiver, id, vp);
}

bool
nsOuterWindowProxy::set(JSContext *cx, JS::Handle<JSObject*> proxy,
                        JS::Handle<jsid> id,
                        JS::Handle<JS::Value> v,
                        JS::Handle<JS::Value> receiver,
                        JS::ObjectOpResult &result) const
{
  if (IsArrayIndex(GetArrayIndexFromId(cx, id))) {
    // Reject the set.  It's up to the caller to decide whether to throw a
    // TypeError.  If the caller is strict mode JS code, it'll throw.
    return result.failReadOnly();
  }

  return js::Wrapper::set(cx, proxy, id, v, receiver, result);
}

bool
nsOuterWindowProxy::getOwnEnumerablePropertyKeys(JSContext *cx, JS::Handle<JSObject*> proxy,
                                                 JS::AutoIdVector &props) const
{
  // BaseProxyHandler::keys seems to do what we want here: call
  // ownPropertyKeys and then filter out the non-enumerable properties.
  return js::BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props);
}

bool
nsOuterWindowProxy::enumerate(JSContext *cx, JS::Handle<JSObject*> proxy,
                              JS::MutableHandle<JSObject*> objp) const
{
  // BaseProxyHandler::enumerate seems to do what we want here: fall
  // back on the property names returned from js::GetPropertyKeys()
  return js::BaseProxyHandler::enumerate(cx, proxy, objp);
}

bool
nsOuterWindowProxy::GetSubframeWindow(JSContext *cx,
                                      JS::Handle<JSObject*> proxy,
                                      JS::Handle<jsid> id,
                                      JS::MutableHandle<JS::Value> vp,
                                      bool& found) const
{
  nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id);
  if (!frame) {
    found = false;
    return true;
  }

  found = true;
  // Just return the window's global
  nsGlobalWindow* global = nsGlobalWindow::Cast(frame);
  frame->EnsureInnerWindow();
  JSObject* obj = global->FastGetGlobalJSObject();
  // This null check fixes a hard-to-reproduce crash that occurs when we
  // get here when we're mid-call to nsDocShell::Destroy. See bug 640904
  // comment 105.
  if (MOZ_UNLIKELY(!obj)) {
    return xpc::Throw(cx, NS_ERROR_FAILURE);
  }
  JS::ExposeObjectToActiveJS(obj);
  vp.setObject(*obj);
  return JS_WrapValue(cx, vp);
}

already_AddRefed<nsPIDOMWindowOuter>
nsOuterWindowProxy::GetSubframeWindow(JSContext *cx,
                                      JS::Handle<JSObject*> proxy,
                                      JS::Handle<jsid> id) const
{
  uint32_t index = GetArrayIndexFromId(cx, id);
  if (!IsArrayIndex(index)) {
    return nullptr;
  }

  nsGlobalWindow* win = GetOuterWindow(proxy);
  MOZ_ASSERT(win->IsOuterWindow());
  return win->IndexedGetterOuter(index);
}

bool
nsOuterWindowProxy::AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy,
                                               JS::AutoIdVector &props) const
{
  uint32_t length = GetOuterWindow(proxy)->Length();
  MOZ_ASSERT(int32_t(length) >= 0);
  if (!props.reserve(props.length() + length)) {
    return false;
  }
  for (int32_t i = 0; i < int32_t(length); ++i) {
    if (!props.append(INT_TO_JSID(i))) {
      return false;
    }
  }

  return true;
}

bool
nsOuterWindowProxy::watch(JSContext *cx, JS::Handle<JSObject*> proxy,
                          JS::Handle<jsid> id, JS::Handle<JSObject*> callable) const
{
  return js::WatchGuts(cx, proxy, id, callable);
}

bool
nsOuterWindowProxy::unwatch(JSContext *cx, JS::Handle<JSObject*> proxy,
                            JS::Handle<jsid> id) const
{
  return js::UnwatchGuts(cx, proxy, id);
}

void
nsOuterWindowProxy::ObjectMoved(JSObject *obj, const JSObject *old)
{
  nsGlobalWindow* outerWindow = GetOuterWindow(obj);
  if (outerWindow) {
    outerWindow->UpdateWrapper(obj, old);
  }
}

const nsOuterWindowProxy
nsOuterWindowProxy::singleton;

class nsChromeOuterWindowProxy : public nsOuterWindowProxy
{
public:
  constexpr nsChromeOuterWindowProxy() : nsOuterWindowProxy() { }

  const char *className(JSContext *cx, JS::Handle<JSObject*> wrapper) const override;

  static const nsChromeOuterWindowProxy singleton;
};

const char *
nsChromeOuterWindowProxy::className(JSContext *cx,
                                    JS::Handle<JSObject*> proxy) const
{
    MOZ_ASSERT(js::IsProxy(proxy));

    return "ChromeWindow";
}

const nsChromeOuterWindowProxy
nsChromeOuterWindowProxy::singleton;

static JSObject*
NewOuterWindowProxy(JSContext *cx, JS::Handle<JSObject*> global, bool isChrome)
{
  JSAutoCompartment ac(cx, global);
  MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(global) == global);

  js::WrapperOptions options;
  options.setClass(&OuterWindowProxyClass);
  options.setSingleton(true);
  JSObject *obj = js::Wrapper::New(cx, global,
                                   isChrome ? &nsChromeOuterWindowProxy::singleton
                                            : &nsOuterWindowProxy::singleton,
                                   options);
  MOZ_ASSERT_IF(obj, js::IsWindowProxy(obj));
  return obj;
}

//*****************************************************************************
//***    nsGlobalWindow: Object Management
//*****************************************************************************

nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow)
  : nsPIDOMWindow<nsISupports>(aOuterWindow ? aOuterWindow->AsOuter() : nullptr),
    mIdleFuzzFactor(0),
    mIdleCallbackIndex(-1),
    mCurrentlyIdle(false),
    mAddActiveEventFuzzTime(true),
    mFullScreen(false),
    mFullscreenMode(false),
    mIsClosed(false),
    mInClose(false),
    mHavePendingClose(false),
    mHadOriginalOpener(false),
    mOriginalOpenerWasSecureContext(false),
    mIsPopupSpam(false),
    mBlockScriptedClosingFlag(false),
    mWasOffline(false),
    mHasHadSlowScript(false),
    mNotifyIdleObserversIdleOnThaw(false),
    mNotifyIdleObserversActiveOnThaw(false),
    mCreatingInnerWindow(false),
    mIsChrome(false),
    mCleanMessageManager(false),
    mNeedsFocus(true),
    mHasFocus(false),
    mShowFocusRingForContent(false),
    mFocusByKeyOccurred(false),
    mHasGamepad(false),
    mHasVREvents(false),
#ifdef MOZ_GAMEPAD
    mHasSeenGamepadInput(false),
#endif
    mNotifiedIDDestroyed(false),
    mAllowScriptsToClose(false),
    mTimeoutInsertionPoint(nullptr),
    mTimeoutIdCounter(1),
    mTimeoutFiringDepth(0),
    mSuspendDepth(0),
    mFreezeDepth(0),
    mFocusMethod(0),
    mSerial(0),
    mIdleCallbackTimeoutCounter(1),
    mIdleRequestCallbackCounter(1),
#ifdef DEBUG
    mSetOpenerWindowCalled(false),
#endif
#ifdef MOZ_B2G
    mNetworkUploadObserverEnabled(false),
    mNetworkDownloadObserverEnabled(false),
#endif
    mCleanedUp(false),
    mDialogAbuseCount(0),
    mAreDialogsEnabled(true),
#ifdef DEBUG
    mIsValidatingTabGroup(false),
#endif
    mCanSkipCCGeneration(0)
{
  AssertIsOnMainThread();

  nsLayoutStatics::AddRef();

  // Initialize the PRCList (this).
  PR_INIT_CLIST(this);

  if (aOuterWindow) {
    // |this| is an inner window, add this inner window to the outer
    // window list of inners.
    PR_INSERT_AFTER(this, aOuterWindow);

    mObserver = new nsGlobalWindowObserver(this);
    if (mObserver) {
      nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
      if (os) {
        // Watch for online/offline status changes so we can fire events. Use
        // a strong reference.
        os->AddObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
                        false);

        // Watch for dom-storage2-changed so we can fire storage
        // events. Use a strong reference.
        os->AddObserver(mObserver, "dom-storage2-changed", false);
      }

      Preferences::AddStrongObserver(mObserver, "intl.accept_languages");
    }
  } else {
    // |this| is an outer window. Outer windows start out frozen and
    // remain frozen until they get an inner window.
    MOZ_ASSERT(IsFrozen());
  }

  // We could have failed the first time through trying
  // to create the entropy collector, so we should
  // try to get one until we succeed.

  gRefCnt++;

  static bool sFirstTime = true;
  if (sFirstTime) {
    Preferences::AddIntVarCache(&gMinTimeoutValue,
                                "dom.min_timeout_value",
                                DEFAULT_MIN_TIMEOUT_VALUE);
    Preferences::AddIntVarCache(&gMinBackgroundTimeoutValue,
                                "dom.min_background_timeout_value",
                                DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE);
    Preferences::AddBoolVarCache(&sIdleObserversAPIFuzzTimeDisabled,
                                 "dom.idle-observers-api.fuzz_time.disabled",
                                 false);

    Preferences::AddUintVarCache(&gThrottledIdlePeriodLength,
                                 "dom.idle_period.throttled_length",
                                 DEFAULT_THROTTLED_IDLE_PERIOD_LENGTH);
    sFirstTime = false;
  }

  if (gDumpFile == nullptr) {
    const nsAdoptingCString& fname =
      Preferences::GetCString("browser.dom.window.dump.file");
    if (!fname.IsEmpty()) {
      // if this fails to open, Dump() knows to just go to stdout
      // on null.
      gDumpFile = fopen(fname, "wb+");
    } else {
      gDumpFile = stdout;
    }
  }

  mSerial = ++gSerialCounter;

#ifdef DEBUG
  if (!PR_GetEnv("MOZ_QUIET")) {
    printf_stderr("++DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p]\n",
                  gRefCnt,
                  static_cast<void*>(ToCanonicalSupports(this)),
                  getpid(),
                  gSerialCounter,
                  static_cast<void*>(ToCanonicalSupports(aOuterWindow)));
  }
#endif

  if (gDOMLeakPRLog)
    MOZ_LOG(gDOMLeakPRLog, LogLevel::Debug,
           ("DOMWINDOW %p created outer=%p", this, aOuterWindow));

  NS_ASSERTION(sWindowsById, "Windows hash table must be created!");
  NS_ASSERTION(!sWindowsById->Get(mWindowID),
               "This window shouldn't be in the hash table yet!");
  // We seem to see crashes in release builds because of null |sWindowsById|.
  if (sWindowsById) {
    sWindowsById->Put(mWindowID, this);
  }
}

#ifdef DEBUG

/* static */
void
nsGlobalWindow::AssertIsOnMainThread()
{
  MOZ_ASSERT(NS_IsMainThread());
}

#endif // DEBUG

/* static */
void
nsGlobalWindow::Init()
{
  AssertIsOnMainThread();

  NS_ASSERTION(gDOMLeakPRLog, "gDOMLeakPRLog should have been initialized!");

  sWindowsById = new WindowByIdTable();
}

nsGlobalWindow::~nsGlobalWindow()
{
  AssertIsOnMainThread();

  DisconnectEventTargetObjects();

  // We have to check if sWindowsById isn't null because ::Shutdown might have
  // been called.
  if (sWindowsById) {
    NS_ASSERTION(sWindowsById->Get(mWindowID),
                 "This window should be in the hash table");
    sWindowsById->Remove(mWindowID);
  }

  --gRefCnt;

#ifdef DEBUG
  if (!PR_GetEnv("MOZ_QUIET")) {
    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);
      }
    }

    nsGlobalWindow* outer = nsGlobalWindow::Cast(mOuterWindow);
    printf_stderr("--DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p] [url = %s]\n",
                  gRefCnt,
                  static_cast<void*>(ToCanonicalSupports(this)),
                  getpid(),
                  mSerial,
                  static_cast<void*>(ToCanonicalSupports(outer)),
                  url.get());
  }
#endif

  if (gDOMLeakPRLog)
    MOZ_LOG(gDOMLeakPRLog, LogLevel::Debug,
           ("DOMWINDOW %p destroyed", this));

  if (IsOuterWindow()) {
    JSObject *proxy = GetWrapperPreserveColor();
    if (proxy) {
      js::SetProxyExtra(proxy, 0, js::PrivateValue(nullptr));
    }

    // An outer window is destroyed with inner windows still possibly
    // alive, iterate through the inner windows and null out their
    // back pointer to this outer, and pull them out of the list of
    // inner windows.

    nsGlobalWindow *w;
    while ((w = (nsGlobalWindow *)PR_LIST_HEAD(this)) != this) {
      PR_REMOVE_AND_INIT_LINK(w);
    }

    DropOuterWindowDocs();
  } else {
    Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS,
                          mMutationBits ? 1 : 0);

    if (mListenerManager) {
      mListenerManager->Disconnect();
      mListenerManager = nullptr;
    }

    // An inner window is destroyed, pull it out of the outer window's
    // list if inner windows.

    PR_REMOVE_LINK(this);

    // If our outer window's inner window is this window, null out the
    // outer window's reference to this window that's being deleted.
    nsGlobalWindow *outer = GetOuterWindowInternal();
    if (outer) {
      outer->MaybeClearInnerWindow(this);
    }
  }

  // We don't have to leave the tab group if we are an inner window.
  if (mTabGroup && IsOuterWindow()) {
    mTabGroup->Leave(AsOuter());
  }

  // Outer windows are always supposed to call CleanUp before letting themselves
  // be destroyed. And while CleanUp generally seems to be intended to clean up
  // outers, we've historically called it for both. Changing this would probably
  // involve auditing all of the references that inners and outers can have, and
  // separating the handling into CleanUp() and FreeInnerObjects.
  if (IsInnerWindow()) {
    CleanUp();
  } else {
    MOZ_ASSERT(mCleanedUp);
  }

  nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
  if (ac)
    ac->RemoveWindowAsListener(this);

  nsLayoutStatics::Release();
}

void
nsGlobalWindow::AddEventTargetObject(DOMEventTargetHelper* aObject)
{
  MOZ_ASSERT(IsInnerWindow());
  mEventTargetObjects.PutEntry(aObject);
}

void
nsGlobalWindow::RemoveEventTargetObject(DOMEventTargetHelper* aObject)
{
  MOZ_ASSERT(IsInnerWindow());
  mEventTargetObjects.RemoveEntry(aObject);
}

void
nsGlobalWindow::DisconnectEventTargetObjects()
{
  for (auto iter = mEventTargetObjects.ConstIter(); !iter.Done();
       iter.Next()) {
    RefPtr<DOMEventTargetHelper> target = iter.Get()->GetKey();
    target->DisconnectFromOwner();
  }
  mEventTargetObjects.Clear();
}

// static
void
nsGlobalWindow::ShutDown()
{
  AssertIsOnMainThread();

  if (gDumpFile && gDumpFile != stdout) {
    fclose(gDumpFile);
  }
  gDumpFile = nullptr;

  delete sWindowsById;
  sWindowsById = nullptr;
}

// static
void
nsGlobalWindow::CleanupCachedXBLHandlers(nsGlobalWindow* aWindow)
{
  if (aWindow->mCachedXBLPrototypeHandlers &&
      aWindow->mCachedXBLPrototypeHandlers->Count() > 0) {
    aWindow->mCachedXBLPrototypeHandlers->Clear();
  }
}

void
nsGlobalWindow::MaybeForgiveSpamCount()
{
  if (IsOuterWindow() &&
      IsPopupSpamWindow()) {
    SetIsPopupSpamWindow(false);
  }
}

void
nsGlobalWindow::SetIsPopupSpamWindow(bool aIsPopupSpam)
{
  MOZ_ASSERT(IsOuterWindow());

  mIsPopupSpam = aIsPopupSpam;
  if (aIsPopupSpam) {
    ++gOpenPopupSpamCount;
  } else {
    --gOpenPopupSpamCount;
    NS_ASSERTION(gOpenPopupSpamCount >= 0,
                 "Unbalanced decrement of gOpenPopupSpamCount");
  }
}

void
nsGlobalWindow::DropOuterWindowDocs()
{
  MOZ_ASSERT(IsOuterWindow());
  MOZ_ASSERT_IF(mDoc, !mDoc->EventHandlingSuppressed());
  mDoc = nullptr;
  mSuspendedDoc = nullptr;
}

void
nsGlobalWindow::CleanUp()
{
  // Guarantee idempotence.
  if (mCleanedUp)
    return;
  mCleanedUp = true;

  StartDying();

  DisconnectEventTargetObjects();

  if (mObserver) {
    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
    if (os) {
      os->RemoveObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
      os->RemoveObserver(mObserver, "dom-storage2-changed");
    }

#ifdef MOZ_B2G
    DisableNetworkEvent(eNetworkUpload);
    DisableNetworkEvent(eNetworkDownload);
#endif // MOZ_B2G

    if (mIdleService) {
      mIdleService->RemoveIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S);
    }

    Preferences::RemoveObserver(mObserver, "intl.accept_languages");

    // Drop its reference to this dying window, in case for some bogus reason
    // the object stays around.
    mObserver->Forget();
  }

  if (mNavigator) {
    mNavigator->Invalidate();
    mNavigator = nullptr;
  }

  mScreen = nullptr;
  mMenubar = nullptr;
  mToolbar = nullptr;
  mLocationbar = nullptr;
  mPersonalbar = nullptr;
  mStatusbar = nullptr;
  mScrollbars = nullptr;
  mLocation = nullptr;
  mHistory = nullptr;
  mCustomElements = nullptr;
  mFrames = nullptr;
  mWindowUtils = nullptr;
  mApplicationCache = nullptr;
  mIndexedDB = nullptr;

  mConsole = nullptr;

  mExternal = nullptr;

  mMozSelfSupport = nullptr;

  mPerformance = nullptr;

#ifdef MOZ_WEBSPEECH
  mSpeechSynthesis = nullptr;
#endif

#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
  mOrientationChangeObserver = nullptr;
#endif

  ClearControllers();

  mOpener = nullptr;             // Forces Release
  if (mContext) {
    mContext = nullptr;            // Forces Release
  }
  mChromeEventHandler = nullptr; // Forces Release
  mParentTarget = nullptr;

  if (IsOuterWindow()) {
    nsGlobalWindow* inner = GetCurrentInnerWindowInternal();
    if (inner) {
      inner->CleanUp();
    }
  }

  if (IsInnerWindow()) {
    DisableGamepadUpdates();
    mHasGamepad = false;
    DisableVRUpdates();
    mHasVREvents = false;
#ifdef MOZ_B2G
    DisableTimeChangeNotifications();
#endif
    DisableIdleCallbackRequests();
  } else {
    MOZ_ASSERT(!mHasGamepad);
    MOZ_ASSERT(!mHasVREvents);
  }

  if (mCleanMessageManager) {
    MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned");
    nsGlobalChromeWindow *asChrome = static_cast<nsGlobalChromeWindow*>(this);
    if (asChrome->mMessageManager) {
      static_cast<nsFrameMessageManager*>(
        asChrome->mMessageManager.get())->Disconnect();
    }
  }

  mArguments = nullptr;
  mDialogArguments = nullptr;

  CleanupCachedXBLHandlers(this);

  for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
    mAudioContexts[i]->Shutdown();
  }
  mAudioContexts.Clear();

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

  mServiceWorkerRegistrationTable.Clear();
}

void
nsGlobalWindow::ClearControllers()
{
  if (mControllers) {
    uint32_t count;
    mControllers->GetControllerCount(&count);

    while (count--) {
      nsCOMPtr<nsIController> controller;
      mControllers->GetControllerAt(count, getter_AddRefs(controller));

      nsCOMPtr<nsIControllerContext> context = do_QueryInterface(controller);
      if (context)
        context->SetCommandContext(nullptr);
    }

    mControllers = nullptr;
  }
}

void
nsGlobalWindow::FreeInnerObjects()
{
  NS_ASSERTION(IsInnerWindow(), "Don't free inner objects on an outer window");

  // Make sure that this is called before we null out the document and
  // other members that the window destroyed observers could
  // re-create.
  NotifyDOMWindowDestroyed(this);
  if (auto* reporter = nsWindowMemoryReporter::Get()) {
    reporter->ObserveDOMWindowDetached(this);
  }

  mInnerObjectsFreed = true;

  // Kill all of the workers for this window.
  mozilla::dom::workers::CancelWorkersForWindow(AsInner());

  ClearAllTimeouts();

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

  mIdleObservers.Clear();

  DisableIdleCallbackRequests();

  mChromeEventHandler = nullptr;

  if (mListenerManager) {
    mListenerManager->Disconnect();
    mListenerManager = nullptr;
  }

  mLocation = nullptr;
  mHistory = nullptr;
  mCustomElements = nullptr;

  if (mNavigator) {
    mNavigator->OnNavigation();
    mNavigator->Invalidate();
    mNavigator = nullptr;
  }

  if (mScreen) {
    mScreen = nullptr;
  }

#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
  mOrientationChangeObserver = nullptr;
#endif

  if (mDoc) {
    // Remember the document's principal and URI.
    mDocumentPrincipal = mDoc->NodePrincipal();
    mDocumentURI = mDoc->GetDocumentURI();
    mDocBaseURI = mDoc->GetDocBaseURI();

    while (mDoc->EventHandlingSuppressed()) {
      mDoc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eEvents, false);
    }

    // Note: we don't have to worry about eAnimationsOnly suppressions because
    // they won't leak.
  }

  // Remove our reference to the document and the document principal.
  mFocusedNode = nullptr;

  if (mApplicationCache) {
    static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->Disconnect();
    mApplicationCache = nullptr;
  }

  mIndexedDB = nullptr;

  UnlinkHostObjectURIs();

  NotifyWindowIDDestroyed("inner-window-destroyed");

  CleanupCachedXBLHandlers(this);

  for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
    mAudioContexts[i]->Shutdown();
  }
  mAudioContexts.Clear();

#ifdef MOZ_GAMEPAD
  DisableGamepadUpdates();
  mHasGamepad = false;
  mGamepads.Clear();
#endif
  DisableVRUpdates();
  mHasVREvents = false;
  mVRDisplays.Clear();
}

//*****************************************************************************
// nsGlobalWindow::nsISupports
//*****************************************************************************

// QueryInterface implementation for nsGlobalWindow
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGlobalWindow)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  // Make sure this matches the cast in nsGlobalWindow::FromWrapper()
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventTarget)
  NS_INTERFACE_MAP_ENTRY(nsIDOMWindow)
  if (aIID.Equals(NS_GET_IID(nsIDOMWindowInternal))) {
    foundInterface = static_cast<nsIDOMWindowInternal*>(this);
    if (!sWarnedAboutWindowInternal) {
      sWarnedAboutWindowInternal = true;
      nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                      NS_LITERAL_CSTRING("Extensions"), mDoc,
                                      nsContentUtils::eDOM_PROPERTIES,
                                      "nsIDOMWindowInternalWarning");
    }
  } else
  NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
  NS_INTERFACE_MAP_ENTRY(nsIScriptGlobalObject)
  NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
  NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget)
  NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget)
  if (aIID.Equals(NS_GET_IID(nsPIDOMWindowInner))) {
    foundInterface = AsInner();
  } else
  if (aIID.Equals(NS_GET_IID(mozIDOMWindow)) && IsInnerWindow()) {
    foundInterface = AsInner();
  } else
  if (aIID.Equals(NS_GET_IID(nsPIDOMWindowOuter))) {
    foundInterface = AsOuter();
  } else
  if (aIID.Equals(NS_GET_IID(mozIDOMWindowProxy)) && IsOuterWindow()) {
    foundInterface = AsOuter();
  } else
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
  NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_END


NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGlobalWindow)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGlobalWindow)

NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGlobalWindow)
  if (tmp->IsBlackForCC(false)) {
    if (nsCCUncollectableMarker::InGeneration(tmp->mCanSkipCCGeneration)) {
      return true;
    }
    tmp->mCanSkipCCGeneration = nsCCUncollectableMarker::sGeneration;
    if (tmp->mCachedXBLPrototypeHandlers) {
      for (auto iter = tmp->mCachedXBLPrototypeHandlers->Iter();
           !iter.Done();
           iter.Next()) {
        iter.Data().exposeToActiveJS();
      }
    }
    if (EventListenerManager* elm = tmp->GetExistingListenerManager()) {
      elm->MarkForCC();
    }
    tmp->UnmarkGrayTimers();
    return true;
  }
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END

NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsGlobalWindow)
  return tmp->IsBlackForCC(true);
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END

NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsGlobalWindow)
  return tmp->IsBlackForCC(false);
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END

inline void
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
                            IdleObserverHolder& aField,
                            const char* aName,
                            unsigned aFlags)
{
  CycleCollectionNoteChild(aCallback, aField.mIdleObserver.get(), aName, aFlags);
}

NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalWindow)

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow)
  if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
    char name[512];
    nsAutoCString uri;
    if (tmp->mDoc && tmp->mDoc->GetDocumentURI()) {
      uri = tmp->mDoc->GetDocumentURI()->GetSpecOrDefault();
    }
    SprintfLiteral(name, "nsGlobalWindow # %" PRIu64 " %s %s", tmp->mWindowID,
                   tmp->IsInnerWindow() ? "inner" : "outer", uri.get());
    cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
  } else {
    NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsGlobalWindow, tmp->mRefCnt.get())
  }

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mArguments)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDialogArguments)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReturnValue)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNavigator)

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance)

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerRegistrationTable)

#ifdef MOZ_WEBSPEECH
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSpeechSynthesis)
#endif

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOuterWindow)

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)

  for (Timeout* timeout = tmp->mTimeouts.getFirst();
       timeout;
       timeout = timeout->getNext()) {
    cb.NoteNativeChild(timeout, NS_CYCLE_COLLECTION_PARTICIPANT(Timeout));
  }

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocation)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHistory)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCustomElements)

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStorage)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionStorage)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplicationCache)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedDoc)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexedDB)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleService)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWakeLock)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingStorageEvents)

  for (IdleRequest* request : tmp->mIdleRequestCallbacks) {
    cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest));
  }

  for (IdleRequest* request : tmp->mThrottledIdleRequestCallbacks) {
    cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest));
  }

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleObservers)

#ifdef MOZ_GAMEPAD
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepads)
#endif

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheStorage)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRDisplays)

  // Traverse stuff from nsPIDOMWindow
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeEventHandler)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentTarget)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameElement)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusedNode)

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMenubar)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mToolbar)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocationbar)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPersonalbar)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStatusbar)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollbars)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrypto)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mU2F)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsole)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExternal)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMozSelfSupport)

  tmp->TraverseHostObjectURIs(cb);

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow)
  nsGlobalWindow::CleanupCachedXBLHandlers(tmp);

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mArguments)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDialogArguments)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mReturnValue)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigator)

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance)

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mServiceWorkerRegistrationTable)

#ifdef MOZ_WEBSPEECH
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSpeechSynthesis)
#endif

  if (tmp->mOuterWindow) {
    nsGlobalWindow::Cast(tmp->mOuterWindow)->MaybeClearInnerWindow(tmp);
    NS_IMPL_CYCLE_COLLECTION_UNLINK(mOuterWindow)
  }

  if (tmp->mListenerManager) {
    tmp->mListenerManager->Disconnect();
    NS_IMPL_CYCLE_COLLECTION_UNLINK(mListenerManager)
  }

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mHistory)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCustomElements)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStorage)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionStorage)
  if (tmp->mApplicationCache) {
    static_cast<nsDOMOfflineResourceList*>(tmp->mApplicationCache.get())->Disconnect();
    NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplicationCache)
  }
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuspendedDoc)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexedDB)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleService)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWakeLock)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingStorageEvents)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleObservers)

#ifdef MOZ_GAMEPAD
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mGamepads)
#endif

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCacheStorage)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mVRDisplays)

  // Unlink stuff from nsPIDOMWindow
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeEventHandler)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentTarget)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameElement)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mFocusedNode)

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mMenubar)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mToolbar)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocationbar)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPersonalbar)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mStatusbar)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollbars)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrypto)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mU2F)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mExternal)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mMozSelfSupport)

  tmp->UnlinkHostObjectURIs();

  tmp->DisableIdleCallbackRequests();

  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

#ifdef DEBUG
void
nsGlobalWindow::RiskyUnlink()
{
  NS_CYCLE_COLLECTION_INNERNAME.Unlink(this);
}
#endif

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsGlobalWindow)
  if (tmp->mCachedXBLPrototypeHandlers) {
    for (auto iter = tmp->mCachedXBLPrototypeHandlers->Iter();
         !iter.Done();
         iter.Next()) {
      aCallbacks.Trace(&iter.Data(), "Cached XBL prototype handler", aClosure);
    }
  }
  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END

bool
nsGlobalWindow::IsBlackForCC(bool aTracingNeeded)
{
  if (!nsCCUncollectableMarker::sGeneration) {
    return false;
  }

  return (nsCCUncollectableMarker::InGeneration(GetMarkedCCGeneration()) ||
          IsBlack()) &&
         (!aTracingNeeded ||
          HasNothingToTrace(static_cast<nsIDOMEventTarget*>(this)));
}

void
nsGlobalWindow::UnmarkGrayTimers()
{
  for (Timeout* timeout = mTimeouts.getFirst();
       timeout;
       timeout = timeout->getNext()) {
    if (timeout->mScriptHandler) {
      timeout->mScriptHandler->MarkForCC();
    }
  }
}

//*****************************************************************************
// nsGlobalWindow::nsIScriptGlobalObject
//*****************************************************************************

nsresult
nsGlobalWindow::EnsureScriptEnvironment()
{
  nsGlobalWindow* outer = GetOuterWindowInternal();
  if (!outer) {
    NS_WARNING("No outer window available!");
    return NS_ERROR_FAILURE;
  }

  if (outer->GetWrapperPreserveColor()) {
    return NS_OK;
  }

  NS_ASSERTION(!outer->GetCurrentInnerWindowInternal(),
               "No cached wrapper, but we have an inner window?");

  // If this window is a [i]frame, don't bother GC'ing when the frame's context
  // is destroyed since a GC will happen when the frameset or host document is
  // destroyed anyway.
  nsCOMPtr<nsIScriptContext> context = new nsJSContext(!IsFrame(), outer);

  NS_ASSERTION(!outer->mContext, "Will overwrite mContext!");

  // should probably assert the context is clean???
  context->WillInitializeContext();

  nsresult rv = context->InitContext();
  NS_ENSURE_SUCCESS(rv, rv);

  outer->mContext = context;
  return NS_OK;
}

nsIScriptContext *
nsGlobalWindow::GetScriptContext()
{
  nsGlobalWindow* outer = GetOuterWindowInternal();
  if (!outer) {
    return nullptr;
  }
  return outer->mContext;
}

JSObject *
nsGlobalWindow::GetGlobalJSObject()
{
  return FastGetGlobalJSObject();
}

void
nsGlobalWindow::TraceGlobalJSObject(JSTracer* aTrc)
{
  TraceWrapper(aTrc, "active window global");
}

bool
nsGlobalWindow::WouldReuseInnerWindow(nsIDocument* aNewDocument)
{
  MOZ_ASSERT(IsOuterWindow());

  // We reuse the inner window when:
  // a. We are currently at our original document.
  // b. At least one of the following conditions are true:
  // -- The new document is the same as the old document. This means that we're
  //    getting called from document.open().
  // -- The new document has the same origin as what we have loaded right now.

  if (!mDoc || !aNewDocument) {
    return false;
  }

  if (!mDoc->IsInitialDocument()) {
    return false;
  }

  NS_ASSERTION(NS_IsAboutBlank(mDoc->GetDocumentURI()),
               "How'd this happen?");

  // Great, we're the original document, check for one of the other
  // conditions.

  if (mDoc == aNewDocument) {
    return true;
  }

  bool equal;
  if (NS_SUCCEEDED(mDoc->NodePrincipal()->Equals(aNewDocument->NodePrincipal(),
                                                 &equal)) &&
      equal) {
    // The origin is the same.
    return true;
  }

  return false;
}

void
nsGlobalWindow::SetInitialPrincipalToSubject()
{
  MOZ_ASSERT(IsOuterWindow());

  // First, grab the subject principal.
  nsCOMPtr<nsIPrincipal> newWindowPrincipal = nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller();

  // We should never create windows with an expanded principal.
  // If we have a system principal, make sure we're not using it for a content
  // docshell.
  // NOTE: Please keep this logic in sync with nsWebShellWindow::Initialize().
  if (nsContentUtils::IsExpandedPrincipal(newWindowPrincipal) ||
      (nsContentUtils::IsSystemPrincipal(newWindowPrincipal) &&
       GetDocShell()->ItemType() != nsIDocShellTreeItem::typeChrome)) {
    newWindowPrincipal = nullptr;
  }

  // If there's an existing document, bail if it either:
  if (mDoc) {
    // (a) is not an initial about:blank document, or
    if (!mDoc->IsInitialDocument())
      return;
    // (b) already has the correct principal.
    if (mDoc->NodePrincipal() == newWindowPrincipal)
      return;

#ifdef DEBUG
    // If we have a document loaded at this point, it had better be about:blank.
    // Otherwise, something is really weird.
    nsCOMPtr<nsIURI> uri;
    mDoc->NodePrincipal()->GetURI(getter_AddRefs(uri));
    NS_ASSERTION(uri && NS_IsAboutBlank(uri) &&
                 NS_IsAboutBlank(mDoc->GetDocumentURI()),
                 "Unexpected original document");
#endif
  }

  GetDocShell()->CreateAboutBlankContentViewer(newWindowPrincipal);
  mDoc->SetIsInitialDocument(true);

  nsCOMPtr<nsIPresShell> shell = GetDocShell()->GetPresShell();

  if (shell && !shell->DidInitialize()) {
    // Ensure that if someone plays with this document they will get
    // layout happening.
    nsRect r = shell->GetPresContext()->GetVisibleArea();
    shell->Initialize(r.width, r.height);
  }
}

PopupControlState
PushPopupControlState(PopupControlState aState, bool aForce)
{
  MOZ_ASSERT(NS_IsMainThread());

  PopupControlState oldState = gPopupControlState;

  if (aState < gPopupControlState || aForce) {
    gPopupControlState = aState;
  }

  return oldState;
}

void
PopPopupControlState(PopupControlState aState)
{
  MOZ_ASSERT(NS_IsMainThread());

  gPopupControlState = aState;
}

PopupControlState
nsGlobalWindow::PushPopupControlState(PopupControlState aState,
                                      bool aForce) const
{
  return ::PushPopupControlState(aState, aForce);
}

void
nsGlobalWindow::PopPopupControlState(PopupControlState aState) const
{
  ::PopPopupControlState(aState);
}

PopupControlState
nsGlobalWindow::GetPopupControlState() const
{
  MOZ_ASSERT(NS_IsMainThread());
  return gPopupControlState;
}

#define WINDOWSTATEHOLDER_IID \
{0x0b917c3e, 0xbd50, 0x4683, {0xaf, 0xc9, 0xc7, 0x81, 0x07, 0xae, 0x33, 0x26}}

class WindowStateHolder final : public nsISupports
{
public:
  NS_DECLARE_STATIC_IID_ACCESSOR(WINDOWSTATEHOLDER_IID)
  NS_DECL_ISUPPORTS

  explicit WindowStateHolder(nsGlobalWindow *aWindow);

  nsGlobalWindow* GetInnerWindow() { return mInnerWindow; }

  void DidRestoreWindow()
  {
    mInnerWindow = nullptr;
    mInnerWindowReflector = nullptr;
  }

protected:
  ~WindowStateHolder();

  nsGlobalWindow *mInnerWindow;
  // We hold onto this to make sure the inner window doesn't go away. The outer
  // window ends up recalculating it anyway.
  JS::PersistentRooted<JSObject*> mInnerWindowReflector;
};

NS_DEFINE_STATIC_IID_ACCESSOR(WindowStateHolder, WINDOWSTATEHOLDER_IID)

WindowStateHolder::WindowStateHolder(nsGlobalWindow* aWindow)
  : mInnerWindow(aWindow),
    mInnerWindowReflector(RootingCx(), aWindow->GetWrapper())
{
  NS_PRECONDITION(aWindow, "null window");
  NS_PRECONDITION(aWindow->IsInnerWindow(), "Saving an outer window");

  aWindow->Suspend();

  // When a global goes into the bfcache, we disable script.
  xpc::Scriptability::Get(mInnerWindowReflector).SetDocShellAllowsScript(false);
}

WindowStateHolder::~WindowStateHolder()
{
  if (mInnerWindow) {
    // This window was left in the bfcache and is now going away. We need to
    // free it up.
    // Note that FreeInnerObjects may already have been called on the
    // inner window if its outer has already had SetDocShell(null)
    // called.
    mInnerWindow->FreeInnerObjects();
  }
}

NS_IMPL_ISUPPORTS(WindowStateHolder, WindowStateHolder)

// We need certain special behavior for remote XUL whitelisted domains, but we
// don't want that behavior to take effect in automation, because we whitelist
// all the mochitest domains. So we need to check a pref here.
static bool
TreatAsRemoteXUL(nsIPrincipal* aPrincipal)
{
  MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(aPrincipal));
  return nsContentUtils::AllowXULXBLForPrincipal(aPrincipal) &&
         !Preferences::GetBool("dom.use_xbl_scopes_for_remote_xul", false);
}

static bool
EnablePrivilege(JSContext* cx, unsigned argc, JS::Value* vp)
{
  Telemetry::Accumulate(Telemetry::ENABLE_PRIVILEGE_EVER_CALLED, true);
  return xpc::EnableUniversalXPConnect(cx);
}

static const JSFunctionSpec EnablePrivilegeSpec[] = {
  JS_FS("enablePrivilege", EnablePrivilege, 1, 0),
  JS_FS_END
};

static bool
InitializeLegacyNetscapeObject(JSContext* aCx, JS::Handle<JSObject*> aGlobal)
{
  JSAutoCompartment ac(aCx, aGlobal);

  // Note: MathJax depends on window.netscape being exposed. See bug 791526.
  JS::Rooted<JSObject*> obj(aCx);
  obj = JS_DefineObject(aCx, aGlobal, "netscape", nullptr);
  NS_ENSURE_TRUE(obj, false);

  obj = JS_DefineObject(aCx, obj, "security", nullptr);
  NS_ENSURE_TRUE(obj, false);

  // We hide enablePrivilege behind a pref because it has been altered in a
  // way that makes it fundamentally insecure to use in production. Mozilla
  // uses this pref during automated testing to support legacy test code that
  // uses enablePrivilege. If you're not doing test automation, you _must_ not
  // flip this pref, or you will be exposing all your users to security
  // vulnerabilities.
  if (!xpc::IsInAutomation()) {
    return true;
  }

  /* Define PrivilegeManager object with the necessary "static" methods. */
  obj = JS_DefineObject(aCx, obj, "PrivilegeManager", nullptr);
  NS_ENSURE_TRUE(obj, false);

  return JS_DefineFunctions(aCx, obj, EnablePrivilegeSpec);
}

bool
nsGlobalWindow::ComputeIsSecureContext(nsIDocument* aDocument)
{
  MOZ_ASSERT(IsOuterWindow());

  nsCOMPtr<nsIPrincipal> principal = aDocument->NodePrincipal();
  if (nsContentUtils::IsSystemPrincipal(principal)) {
    return true;
  }

  // Implement https://w3c.github.io/webappsec-secure-contexts/#settings-object

  bool hadNonSecureContextCreator = false;

  nsPIDOMWindowOuter* parentOuterWin = GetScriptableParent();
  MOZ_ASSERT(parentOuterWin, "How can we get here? No docShell somehow?");
  if (nsGlobalWindow::Cast(parentOuterWin) != this) {
    // There may be a small chance that parentOuterWin has navigated in
    // the time that it took us to start loading this sub-document.  If that
    // were the case then parentOuterWin->GetCurrentInnerWindow() wouldn't
    // return the window for the document that is embedding us.  For this
    // reason we only use the GetScriptableParent call above to check that we
    // have a same-type parent, but actually get the inner window via the
    // document that we know is embedding us.
    nsIDocument* creatorDoc = aDocument->GetParentDocument();
    if (!creatorDoc) {
      return false; // we must be tearing down
    }
    nsGlobalWindow* parentWin =
      nsGlobalWindow::Cast(creatorDoc->GetInnerWindow());
    if (!parentWin) {
      return false; // we must be tearing down
    }
    MOZ_ASSERT(parentWin ==
               nsGlobalWindow::Cast(parentOuterWin->GetCurrentInnerWindow()),
               "Creator window mismatch while setting Secure Context state");
    hadNonSecureContextCreator = !parentWin->IsSecureContext();
  } else if (mHadOriginalOpener) {
    hadNonSecureContextCreator = !mOriginalOpenerWasSecureContext;
  }

  if (hadNonSecureContextCreator) {
    return false;
  }

  if (nsContentUtils::HttpsStateIsModern(aDocument)) {
    return true;
  }

  if (principal->GetIsNullPrincipal()) {
    nsCOMPtr<nsIURI> uri = aDocument->GetOriginalURI();
    // IsOriginPotentiallyTrustworthy doesn't care about origin attributes so
    // it doesn't actually matter what we use here, but reusing the document
    // principal's attributes is convenient.
    const PrincipalOriginAttributes& attrs =
      BasePrincipal::Cast(principal)->OriginAttributesRef();
    // CreateCodebasePrincipal correctly gets a useful principal for blob: and
    // other URI_INHERITS_SECURITY_CONTEXT URIs.
    principal = BasePrincipal::CreateCodebasePrincipal(uri, attrs);
    if (NS_WARN_IF(!principal)) {
      return false;
    }
  }

  nsCOMPtr<nsIContentSecurityManager> csm =
    do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID);
  NS_WARNING_ASSERTION(csm, "csm is null");
  if (csm) {
    bool isTrustworthyOrigin = false;
    csm->IsOriginPotentiallyTrustworthy(principal, &isTrustworthyOrigin);
    if (isTrustworthyOrigin) {
      return true;
    }
  }

  return false;
}

/**
 * Create a new global object that will be used for an inner window.
 * Return the native global and an nsISupports 'holder' that can be used
 * to manage the lifetime of it.
 */
static nsresult
CreateNativeGlobalForInner(JSContext* aCx,
                           nsGlobalWindow* aNewInner,
                           nsIURI* aURI,
                           nsIPrincipal* aPrincipal,
                           JS::MutableHandle<JSObject*> aGlobal,
                           bool aIsSecureContext)
{
  MOZ_ASSERT(aCx);
  MOZ_ASSERT(aNewInner);
  MOZ_ASSERT(aNewInner->IsInnerWindow());
  MOZ_ASSERT(aPrincipal);

  // DOMWindow with nsEP is not supported, we have to make sure
  // no one creates one accidentally.
  nsCOMPtr<nsIExpandedPrincipal> nsEP = do_QueryInterface(aPrincipal);
  MOZ_RELEASE_ASSERT(!nsEP, "DOMWindow with nsEP is not supported");

  nsGlobalWindow *top = nullptr;
  if (aNewInner->GetOuterWindow()) {
    top = aNewInner->GetTopInternal();
  }

  JS::CompartmentOptions options;

  // Sometimes add-ons load their own XUL windows, either as separate top-level
  // windows or inside a browser element. In such cases we want to tag the
  // window's compartment with the add-on ID. See bug 1092156.
  if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
    options.creationOptions().setAddonId(MapURIToAddonID(aURI));
  }

  if (top && top->GetGlobalJSObject()) {
    options.creationOptions().setSameZoneAs(top->GetGlobalJSObject());
  }

  options.creationOptions().setSecureContext(aIsSecureContext);

  xpc::InitGlobalObjectOptions(options, aPrincipal);

  // Determine if we need the Components object.
  bool needComponents = nsContentUtils::IsSystemPrincipal(aPrincipal) ||
                        TreatAsRemoteXUL(aPrincipal);
  uint32_t flags = needComponents ? 0 : nsIXPConnect::OMIT_COMPONENTS_OBJECT;
  flags |= nsIXPConnect::DONT_FIRE_ONNEWGLOBALHOOK;

  if (!WindowBinding::Wrap(aCx, aNewInner, aNewInner, options,
                           nsJSPrincipals::get(aPrincipal), false, aGlobal) ||
      !xpc::InitGlobalObject(aCx, aGlobal, flags)) {
    return NS_ERROR_FAILURE;
  }

  MOZ_ASSERT(aNewInner->GetWrapperPreserveColor() == aGlobal);

  // Set the location information for the new global, so that tools like
  // about:memory may use that information
  xpc::SetLocationForGlobal(aGlobal, aURI);

  if (!InitializeLegacyNetscapeObject(aCx, aGlobal)) {
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

nsresult
nsGlobalWindow::SetNewDocument(nsIDocument* aDocument,
                               nsISupports* aState,
                               bool aForceReuseInnerWindow)
{
  NS_PRECONDITION(mDocumentPrincipal == nullptr,
                  "mDocumentPrincipal prematurely set!");
  MOZ_ASSERT(aDocument);

  if (IsInnerWindow()) {
    if (!mOuterWindow) {
      return NS_ERROR_NOT_INITIALIZED;
    }

    // Refuse to set a new document if the call came from an inner
    // window that's not the current inner window.
    if (mOuterWindow->GetCurrentInnerWindow() != AsInner()) {
      return NS_ERROR_NOT_AVAILABLE;
    }

    return GetOuterWindowInternal()->SetNewDocument(aDocument, aState,
                                                    aForceReuseInnerWindow);
  }

  NS_PRECONDITION(IsOuterWindow(), "Must only be called on outer windows");

  // Bail out early if we're in process of closing down the window.
  NS_ENSURE_STATE(!mCleanedUp);

  NS_ASSERTION(!AsOuter()->GetCurrentInnerWindow() ||
               AsOuter()->GetCurrentInnerWindow()->GetExtantDoc() == mDoc,
               "Uh, mDoc doesn't match the current inner window "
               "document!");

  bool wouldReuseInnerWindow = WouldReuseInnerWindow(aDocument);
  if (aForceReuseInnerWindow &&
      !wouldReuseInnerWindow &&
      mDoc &&
      mDoc->NodePrincipal() != aDocument->NodePrincipal()) {
    NS_ERROR("Attempted forced inner window reuse while changing principal");
    return NS_ERROR_UNEXPECTED;
  }

  nsCOMPtr<nsIDocument> oldDoc = mDoc;

  AutoJSAPI jsapi;
  jsapi.Init();
  JSContext *cx = jsapi.cx();

  // Check if we're anywhere near the stack limit before we reach the
  // transplanting code, since it has no good way to handle errors. This uses
  // the untrusted script limit, which is not strictly necessary since no
  // actual script should run.
  bool overrecursed = false;
  JS_CHECK_RECURSION_CONSERVATIVE_DONT_REPORT(cx, overrecursed = true);
  if (overrecursed) {
    NS_WARNING("Overrecursion in SetNewDocument");
    return NS_ERROR_FAILURE;
  }

  if (!mDoc) {
    // First document load.

    // Get our private root. If it is equal to us, then we need to
    // attach our global key bindings that handles browser scrolling
    // and other browser commands.
    nsPIDOMWindowOuter* privateRoot = nsGlobalWindow::GetPrivateRoot();

    if (privateRoot == AsOuter()) {
      nsXBLService::AttachGlobalKeyHandler(mChromeEventHandler);
    }
  }

  /* No mDocShell means we're already been partially closed down.  When that
     happens, setting status isn't a big requirement, so don't. (Doesn't happen
     under normal circumstances, but bug 49615 describes a case.) */

  nsContentUtils::AddScriptRunner(
    NewRunnableMethod(this, &nsGlobalWindow::ClearStatus));

  // Sometimes, WouldReuseInnerWindow() returns true even if there's no inner
  // window (see bug 776497). Be safe.
  bool reUseInnerWindow = (aForceReuseInnerWindow || wouldReuseInnerWindow) &&
                          GetCurrentInnerWindowInternal();

  nsresult rv = NS_OK;

  // We set mDoc even though this is an outer window to avoid
  // having to *always* reach into the inner window to find the
  // document.
  mDoc = aDocument;

  // Take this opportunity to clear mSuspendedDoc. Our old inner window is now
  // responsible for unsuspending it.
  mSuspendedDoc = nullptr;

#ifdef DEBUG
  mLastOpenedURI = aDocument->GetDocumentURI();
#endif

  mContext->WillInitializeContext();

  nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal();

  if (currentInner && currentInner->mNavigator) {
    currentInner->mNavigator->OnNavigation();
  }

  RefPtr<nsGlobalWindow> newInnerWindow;
  bool createdInnerWindow = false;

  bool thisChrome = IsChromeWindow();

  nsCOMPtr<WindowStateHolder> wsh = do_QueryInterface(aState);
  NS_ASSERTION(!aState || wsh, "What kind of weird state are you giving me here?");

  JS::Rooted<JSObject*> newInnerGlobal(cx);
  if (reUseInnerWindow) {
    // We're reusing the current inner window.
    NS_ASSERTION(!currentInner->IsFrozen(),
                 "We should never be reusing a shared inner window");
    newInnerWindow = currentInner;
    newInnerGlobal = currentInner->GetWrapperPreserveColor();

    if (aDocument != oldDoc) {
      JS::ExposeObjectToActiveJS(newInnerGlobal);
    }

    // We're reusing the inner window, but this still counts as a navigation,
    // so all expandos and such defined on the outer window should go away. Force
    // all Xray wrappers to be recomputed.
    JS::Rooted<JSObject*> rootedObject(cx, GetWrapper());
    if (!JS_RefreshCrossCompartmentWrappers(cx, rootedObject)) {
      return NS_ERROR_FAILURE;
    }

    // Inner windows are only reused for same-origin principals, but the principals
    // don't necessarily match exactly. Update the principal on the compartment to
    // match the new document.
    // NB: We don't just call currentInner->RefreshCompartmentPrincipals() here
    // because we haven't yet set its mDoc to aDocument.
    JSCompartment *compartment = js::GetObjectCompartment(newInnerGlobal);
#ifdef DEBUG
    bool sameOrigin = false;
    nsIPrincipal *existing =
      nsJSPrincipals::get(JS_GetCompartmentPrincipals(compartment));
    aDocument->NodePrincipal()->Equals(existing, &sameOrigin);
    MOZ_ASSERT(sameOrigin);
#endif
    MOZ_ASSERT_IF(aDocument == oldDoc,
                  xpc::GetCompartmentPrincipal(compartment) ==
                  aDocument->NodePrincipal());
    if (aDocument != oldDoc) {
      JS_SetCompartmentPrincipals(compartment,
                                  nsJSPrincipals::get(aDocument->NodePrincipal()));
      // Make sure we clear out the old content XBL scope, so the new one will
      // get created with a principal that subsumes our new principal.
      xpc::ClearContentXBLScope(newInnerGlobal);
    }
  } else {
    if (aState) {
      newInnerWindow = wsh->GetInnerWindow();
      newInnerGlobal = newInnerWindow->GetWrapperPreserveColor();
    } else {
      if (thisChrome) {
        newInnerWindow = nsGlobalChromeWindow::Create(this);
      } else if (mIsModalContentWindow) {
        newInnerWindow = nsGlobalModalWindow::Create(this);
      } else {
        newInnerWindow = nsGlobalWindow::Create(this);
      }

      // The outer window is automatically treated as frozen when we
      // null out the inner window. As a result, initializing classes
      // on the new inner won't end up reaching into the old inner
      // window for classes etc.
      //
      // [This happens with Object.prototype when XPConnect creates
      // a temporary global while initializing classes; the reason
      // being that xpconnect creates the temp global w/o a parent
      // and proto, which makes the JS engine look up classes in
      // cx->globalObject, i.e. this outer window].

      mInnerWindow = nullptr;

      mCreatingInnerWindow = true;
      // Every script context we are initialized with must create a
      // new global.
      rv = CreateNativeGlobalForInner(cx, newInnerWindow,
                                      aDocument->GetDocumentURI(),
                                      aDocument->NodePrincipal(),
                                      &newInnerGlobal,
                                      ComputeIsSecureContext(aDocument));
      NS_ASSERTION(NS_SUCCEEDED(rv) && newInnerGlobal &&
                   newInnerWindow->GetWrapperPreserveColor() == newInnerGlobal,
                   "Failed to get script global");

      mCreatingInnerWindow = false;
      createdInnerWindow = true;

      NS_ENSURE_SUCCESS(rv, rv);
    }

    if (currentInner && currentInner->GetWrapperPreserveColor()) {
      if (oldDoc == aDocument) {
        // Move the navigator from the old inner window to the new one since
        // this is a document.write. This is safe from a same-origin point of
        // view because document.write can only be used by the same origin.
        newInnerWindow->mNavigator = currentInner->mNavigator;
        currentInner->mNavigator = nullptr;
        if (newInnerWindow->mNavigator) {
          newInnerWindow->mNavigator->SetWindow(newInnerWindow->AsInner());
        }

        // Make a copy of the old window's performance object on document.open.
        // Note that we have to force eager creation of it here, because we need
        // to grab the current document channel and whatnot before that changes.
        currentInner->AsInner()->CreatePerformanceObjectIfNeeded();
        if (currentInner->mPerformance) {
          newInnerWindow->mPerformance =
            Performance::CreateForMainThread(newInnerWindow->AsInner(),
                                             currentInner->mPerformance->GetDOMTiming(),
                                             currentInner->mPerformance->GetChannel(),
                                             currentInner->mPerformance->GetParentPerformance());
        }
      }

      // Don't free objects on our current inner window if it's going to be
      // held in the bfcache.
      if (!currentInner->IsFrozen()) {
        currentInner->FreeInnerObjects();
      }
    }

    mInnerWindow = newInnerWindow->AsInner();

    if (!GetWrapperPreserveColor()) {
      JS::Rooted<JSObject*> outer(cx,
        NewOuterWindowProxy(cx, newInnerGlobal, thisChrome));
      NS_ENSURE_TRUE(outer, NS_ERROR_FAILURE);

      js::SetProxyExtra(outer, 0, js::PrivateValue(ToSupports(this)));

      // Inform the nsJSContext, which is the canonical holder of the outer.
      mContext->SetWindowProxy(outer);
      mContext->DidInitializeContext();

      SetWrapper(mContext->GetWindowProxy());
    } else {
      JS::ExposeObjectToActiveJS(newInnerGlobal);
      JS::Rooted<JSObject*> outerObject(cx,
        NewOuterWindowProxy(cx, newInnerGlobal, thisChrome));
      if (!outerObject) {
        NS_ERROR("out of memory");
        return NS_ERROR_FAILURE;
      }

      JS::Rooted<JSObject*> obj(cx, GetWrapperPreserveColor());

      js::SetProxyExtra(obj, 0, js::PrivateValue(nullptr));
      js::SetProxyExtra(outerObject, 0, js::PrivateValue(nullptr));

      outerObject = xpc::TransplantObject(cx, obj, outerObject);
      if (!outerObject) {
        NS_ERROR("unable to transplant wrappers, probably OOM");
        return NS_ERROR_FAILURE;
      }

      js::SetProxyExtra(outerObject, 0, js::PrivateValue(ToSupports(this)));

      SetWrapper(outerObject);

      MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(outerObject) == newInnerGlobal);

      // Inform the nsJSContext, which is the canonical holder of the outer.
      mContext->SetWindowProxy(outerObject);
    }

    // Enter the new global's compartment.
    JSAutoCompartment ac(cx, GetWrapperPreserveColor());

    {
      JS::Rooted<JSObject*> outer(cx, GetWrapperPreserveColor());
      js::SetWindowProxy(cx, newInnerGlobal, outer);
    }

    // Set scriptability based on the state of the docshell.
    bool allow = GetDocShell()->GetCanExecuteScripts();
    xpc::Scriptability::Get(GetWrapperPreserveColor()).SetDocShellAllowsScript(allow);

    if (!aState) {
      // Get the "window" property once so it will be cached on our inner.  We
      // have to do this here, not in binding code, because this has to happen
      // after we've created the outer window proxy and stashed it in the outer
      // nsGlobalWindow, so GetWrapperPreserveColor() on that outer
      // nsGlobalWindow doesn't return null and nsGlobalWindow::OuterObject
      // works correctly.
      JS::Rooted<JS::Value> unused(cx);
      if (!JS_GetProperty(cx, newInnerGlobal, "window", &unused)) {
        NS_ERROR("can't create the 'window' property");
        return NS_ERROR_FAILURE;
      }

      // And same thing for the "self" property.
      if (!JS_GetProperty(cx, newInnerGlobal, "self", &unused)) {
        NS_ERROR("can't create the 'self' property");
        return NS_ERROR_FAILURE;
      }
    }
  }

  JSAutoCompartment ac(cx, GetWrapperPreserveColor());

  if (!aState && !reUseInnerWindow) {
    // Loading a new page and creating a new inner window, *not*
    // restoring from session history.

    // Now that both the the inner and outer windows are initialized
    // let the script context do its magic to hook them together.
    MOZ_ASSERT(mContext->GetWindowProxy() == GetWrapperPreserveColor());
#ifdef DEBUG
    JS::Rooted<JSObject*> rootedJSObject(cx, GetWrapperPreserveColor());
    JS::Rooted<JSObject*> proto1(cx), proto2(cx);
    JS_GetPrototype(cx, rootedJSObject, &proto1);
    JS_GetPrototype(cx, newInnerGlobal, &proto2);
    NS_ASSERTION(proto1 == proto2,
                 "outer and inner globals should have the same prototype");
#endif

    mInnerWindow->SyncStateFromParentWindow();
  }

  // Add an extra ref in case we release mContext during GC.
  nsCOMPtr<nsIScriptContext> kungFuDeathGrip(mContext);

  aDocument->SetScriptGlobalObject(newInnerWindow);
  MOZ_ASSERT(newInnerWindow->mTabGroup,
             "We must have a TabGroup cached at this point");

  if (!aState) {
    if (reUseInnerWindow) {

      if (newInnerWindow->mDoc != aDocument) {
        newInnerWindow->mDoc = aDocument;

        // The storage objects contain the URL of the window. We have to
        // recreate them when the innerWindow is reused.
        newInnerWindow->mLocalStorage = nullptr;
        newInnerWindow->mSessionStorage = nullptr;

        newInnerWindow->ClearDocumentDependentSlots(cx);
      }
    } else {
      newInnerWindow->InnerSetNewDocument(cx, aDocument);

      // Initialize DOM classes etc on the inner window.
      JS::Rooted<JSObject*> obj(cx, newInnerGlobal);
      rv = kungFuDeathGrip->InitClasses(obj);
      NS_ENSURE_SUCCESS(rv, rv);
    }

    // If the document comes from a JAR, check if the channel was determined
    // to be unsafe. If so, permanently disable script on the compartment by
    // calling Block() and throwing away the key.
    nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(aDocument->GetChannel());
    if (jarChannel && jarChannel->GetIsUnsafe()) {
      xpc::Scriptability::Get(newInnerGlobal).Block();
    }

    if (mArguments) {
      newInnerWindow->DefineArgumentsProperty(mArguments);
      mArguments = nullptr;
    }

    // Give the new inner window our chrome event handler (since it
    // doesn't have one).
    newInnerWindow->mChromeEventHandler = mChromeEventHandler;
  }

  nsJSContext::PokeGC(JS::gcreason::SET_NEW_DOCUMENT);
  kungFuDeathGrip->DidInitializeContext();

  // We wait to fire the debugger hook until the window is all set up and hooked
  // up with the outer. See bug 969156.
  if (createdInnerWindow) {
    nsContentUtils::AddScriptRunner(
      NewRunnableMethod(newInnerWindow,
                        &nsGlobalWindow::FireOnNewGlobalObject));
  }

  if (newInnerWindow && !newInnerWindow->mHasNotifiedGlobalCreated && mDoc) {
    // We should probably notify. However if this is the, arguably bad,
    // situation when we're creating a temporary non-chrome-about-blank
    // document in a chrome docshell, don't notify just yet. Instead wait
    // until we have a real chrome doc.
    if (!mDocShell ||
        mDocShell->ItemType() != nsIDocShellTreeItem::typeChrome ||
        nsContentUtils::IsSystemPrincipal(mDoc->NodePrincipal())) {
      newInnerWindow->mHasNotifiedGlobalCreated = true;
      nsContentUtils::AddScriptRunner(
        NewRunnableMethod(this, &nsGlobalWindow::DispatchDOMWindowCreated));
    }
  }

  PreloadLocalStorage();

  return NS_OK;
}

void
nsGlobalWindow::PreloadLocalStorage()
{
  MOZ_ASSERT(IsOuterWindow());

  if (!Preferences::GetBool(kStorageEnabled)) {
    return;
  }

  if (IsChromeWindow()) {
    return;
  }

  nsIPrincipal* principal = GetPrincipal();
  if (!principal) {
    return;
  }

  nsresult rv;

  nsCOMPtr<nsIDOMStorageManager> storageManager =
    do_GetService("@mozilla.org/dom/localStorage-manager;1", &rv);
  if (NS_FAILED(rv)) {
    return;
  }

  storageManager->PrecacheStorage(principal);
}

void
nsGlobalWindow::DispatchDOMWindowCreated()
{
  MOZ_ASSERT(IsOuterWindow());

  if (!mDoc) {
    return;
  }

  // Fire DOMWindowCreated at chrome event listeners
  nsContentUtils::DispatchChromeEvent(mDoc, mDoc, NS_LITERAL_STRING("DOMWindowCreated"),
                                      true /* bubbles */,
                                      false /* not cancellable */);

  nsCOMPtr<nsIObserverService> observerService =
    mozilla::services::GetObserverService();
  if (observerService) {
    nsAutoString origin;
    nsIPrincipal* principal = mDoc->NodePrincipal();
    nsContentUtils::GetUTFOrigin(principal, origin);
    observerService->
      NotifyObservers(static_cast<nsIDOMWindow*>(this),
                      nsContentUtils::IsSystemPrincipal(principal) ?
                        "chrome-document-global-created" :
                        "content-document-global-created",
                      origin.get());
  }
}

void
nsGlobalWindow::ClearStatus()
{
  SetStatusOuter(EmptyString());
}

void
nsGlobalWindow::InnerSetNewDocument(JSContext* aCx, nsIDocument* aDocument)
{
  NS_PRECONDITION(IsInnerWindow(), "Must only be called on inner windows");
  MOZ_ASSERT(aDocument);

  if (gDOMLeakPRLog && MOZ_LOG_TEST(gDOMLeakPRLog, LogLevel::Debug)) {
    nsIURI *uri = aDocument->GetDocumentURI();
    PR_LogPrint("DOMWINDOW %p SetNewDocument %s",
                this, uri ? uri->GetSpecOrDefault().get() : "");
  }

  mDoc = aDocument;
  ClearDocumentDependentSlots(aCx);
  mFocusedNode = nullptr;
  mLocalStorage = nullptr;
  mSessionStorage = nullptr;

#ifdef DEBUG
  mLastOpenedURI = aDocument->GetDocumentURI();
#endif

  Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS,
                        mMutationBits ? 1 : 0);

  // Clear our mutation bitfield.
  mMutationBits = 0;
}

void
nsGlobalWindow::SetDocShell(nsIDocShell* aDocShell)
{
  NS_ASSERTION(IsOuterWindow(), "Uh, SetDocShell() called on inner window!");
  MOZ_ASSERT(aDocShell);

  if (aDocShell == mDocShell) {
    return;
  }

  mDocShell = aDocShell; // Weak Reference

  nsCOMPtr<nsPIDOMWindowOuter> parentWindow = GetScriptableParentOrNull();
  MOZ_RELEASE_ASSERT(!parentWindow || !mTabGroup || mTabGroup == Cast(parentWindow)->mTabGroup);

  NS_ASSERTION(!mNavigator, "Non-null mNavigator in outer window!");

  if (mFrames) {
    mFrames->SetDocShell(aDocShell);
  }

  // Get our enclosing chrome shell and retrieve its global window impl, so
  // that we can do some forwarding to the chrome document.
  nsCOMPtr<nsIDOMEventTarget> chromeEventHandler;
  mDocShell->GetChromeEventHandler(getter_AddRefs(chromeEventHandler));
  mChromeEventHandler = do_QueryInterface(chromeEventHandler);
  if (!mChromeEventHandler) {
    // We have no chrome event handler. If we have a parent,
    // get our chrome event handler from the parent. If
    // we don't have a parent, then we need to make a new
    // window root object that will function as a chrome event
    // handler and receive all events that occur anywhere inside
    // our window.
    nsCOMPtr<nsPIDOMWindowOuter> parentWindow = GetParent();
    if (parentWindow.get() != AsOuter()) {
      mChromeEventHandler = parentWindow->GetChromeEventHandler();
    }
    else {
      mChromeEventHandler = NS_NewWindowRoot(AsOuter());
      mIsRootOuterWindow = true;
    }
  }

  bool docShellActive;
  mDocShell->GetIsActive(&docShellActive);
  mIsBackground = !docShellActive;
}

void
nsGlobalWindow::DetachFromDocShell()
{
  NS_ASSERTION(IsOuterWindow(), "Uh, DetachFromDocShell() called on inner window!");

  // DetachFromDocShell means the window is being torn down. Drop our
  // reference to the script context, allowing it to be deleted
  // later. Meanwhile, keep our weak reference to the script object
  // so that it can be retrieved later (until it is finalized by the JS GC).

  NS_ASSERTION(mTimeouts.isEmpty(), "Uh, outer window holds timeouts!");

  // Call FreeInnerObjects on all inner windows, not just the current
  // one, since some could be held by WindowStateHolder objects that
  // are GC-owned.
  for (RefPtr<nsGlobalWindow> inner = (nsGlobalWindow *)PR_LIST_HEAD(this);
       inner != this;
       inner = (nsGlobalWindow*)PR_NEXT_LINK(inner)) {
    MOZ_ASSERT(!inner->mOuterWindow || inner->mOuterWindow == AsOuter());
    inner->FreeInnerObjects();
  }

  if (auto* reporter = nsWindowMemoryReporter::Get()) {
    reporter->ObserveDOMWindowDetached(this);
  }

  NotifyWindowIDDestroyed("outer-window-destroyed");

  nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal();

  if (currentInner) {
    NS_ASSERTION(mDoc, "Must have doc!");

    // Remember the document's principal and URI.
    mDocumentPrincipal = mDoc->NodePrincipal();
    mDocumentURI = mDoc->GetDocumentURI();
    mDocBaseURI = mDoc->GetDocBaseURI();

    // Release our document reference
    DropOuterWindowDocs();
    mFocusedNode = nullptr;
  }

  ClearControllers();

  mChromeEventHandler = nullptr; // force release now

  if (mContext) {
    nsJSContext::PokeGC(JS::gcreason::SET_DOC_SHELL);
    mContext = nullptr;
  }

  mDocShell = nullptr; // Weak Reference

  NS_ASSERTION(!mNavigator, "Non-null mNavigator in outer window!");

  if (mFrames) {
    mFrames->SetDocShell(nullptr);
  }

  MaybeForgiveSpamCount();
  CleanUp();
}

void
nsGlobalWindow::SetOpenerWindow(nsPIDOMWindowOuter* aOpener,
                                bool aOriginalOpener)
{
  FORWARD_TO_OUTER_VOID(SetOpenerWindow, (aOpener, aOriginalOpener));

  nsWeakPtr opener = do_GetWeakReference(aOpener);
  if (opener == mOpener) {
    return;
  }

  NS_ASSERTION(!aOriginalOpener || !mSetOpenerWindowCalled,
               "aOriginalOpener is true, but not first call to "
               "SetOpenerWindow!");
  NS_ASSERTION(aOpener || !aOriginalOpener,
               "Shouldn't set mHadOriginalOpener if aOpener is null");

  mOpener = opener.forget();
  NS_ASSERTION(mOpener || !aOpener, "Opener must support weak references!");

  // Check that the js visible opener matches!
  nsPIDOMWindowOuter* contentOpener = GetSanitizedOpener(aOpener);
  MOZ_RELEASE_ASSERT(!contentOpener || !mTabGroup ||
    mTabGroup == Cast(contentOpener)->mTabGroup);

  if (aOriginalOpener) {
    MOZ_ASSERT(!mHadOriginalOpener,
               "Probably too late to call ComputeIsSecureContext again");
    mHadOriginalOpener = true;
    mOriginalOpenerWasSecureContext =
      aOpener->GetCurrentInnerWindow()->IsSecureContext();
  }

#ifdef DEBUG
  mSetOpenerWindowCalled = true;
#endif
}

static
already_AddRefed<EventTarget>
TryGetTabChildGlobalAsEventTarget(nsISupports *aFrom)
{
  nsCOMPtr<nsIFrameLoaderOwner> frameLoaderOwner = do_QueryInterface(aFrom);
  if (!frameLoaderOwner) {
    return nullptr;
  }

  RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
  if (!frameLoader) {
    return nullptr;
  }

  nsCOMPtr<EventTarget> target = frameLoader->GetTabChildGlobalAsEventTarget();
  return target.forget();
}

void
nsGlobalWindow::UpdateParentTarget()
{
  // Try to get our frame element's tab child global (its in-process message
  // manager).  If that fails, fall back to the chrome event handler's tab
  // child global, and if it doesn't have one, just use the chrome event
  // handler itself.

  nsCOMPtr<Element> frameElement = GetOuterWindow()->GetFrameElementInternal();
  nsCOMPtr<EventTarget> eventTarget =
    TryGetTabChildGlobalAsEventTarget(frameElement);

  if (!eventTarget) {
    nsGlobalWindow* topWin = GetScriptableTopInternal();
    if (topWin) {
      frameElement = topWin->AsOuter()->GetFrameElementInternal();
      eventTarget = TryGetTabChildGlobalAsEventTarget(frameElement);
    }
  }

  if (!eventTarget) {
    eventTarget = TryGetTabChildGlobalAsEventTarget(mChromeEventHandler);
  }

  if (!eventTarget) {
    eventTarget = mChromeEventHandler;
  }

  mParentTarget = eventTarget;
}

EventTarget*
nsGlobalWindow::GetTargetForDOMEvent()
{
  return GetOuterWindowInternal();
}

EventTarget*
nsGlobalWindow::GetTargetForEventTargetChain()
{
  return IsInnerWindow() ? this : GetCurrentInnerWindowInternal();
}

nsresult
nsGlobalWindow::WillHandleEvent(EventChainPostVisitor& aVisitor)
{
  return NS_OK;
}

nsresult
nsGlobalWindow::GetEventTargetParent(EventChainPreVisitor& aVisitor)
{
  NS_PRECONDITION(IsInnerWindow(),
                  "GetEventTargetParent is used on outer window!?");
  EventMessage msg = aVisitor.mEvent->mMessage;

  aVisitor.mCanHandle = true;
  aVisitor.mForceContentDispatch = true; //FIXME! Bug 329119
  if (msg == eResize && aVisitor.mEvent->IsTrusted()) {
    // QIing to window so that we can keep the old behavior also in case
    // a child window is handling resize.
    nsCOMPtr<nsPIDOMWindowInner> window =
      do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
    if (window) {
      mIsHandlingResizeEvent = true;
    }
  } else if (msg == eMouseDown && aVisitor.mEvent->IsTrusted()) {
    gMouseDown = true;
  } else if ((msg == eMouseUp || msg == eDragEnd) &&
             aVisitor.mEvent->IsTrusted()) {
    gMouseDown = false;
    if (gDragServiceDisabled) {
      nsCOMPtr<nsIDragService> ds =
        do_GetService("@mozilla.org/widget/dragservice;1");
      if (ds) {
        gDragServiceDisabled = false;
        ds->Unsuppress();
      }
    }
  }

  aVisitor.mParentTarget = GetParentTarget();

  // Handle 'active' event.
  if (!mIdleObservers.IsEmpty() &&
      aVisitor.mEvent->IsTrusted() &&
      (aVisitor.mEvent->HasMouseEventMessage() ||
       aVisitor.mEvent->HasDragEventMessage())) {
    mAddActiveEventFuzzTime = false;
  }

  return NS_OK;
}

bool
nsGlobalWindow::ShouldPromptToBlockDialogs()
{
  MOZ_ASSERT(IsOuterWindow());

  nsGlobalWindow *topWindow = GetScriptableTopInternal();
  if (!topWindow) {
    NS_ASSERTION(!mDocShell, "ShouldPromptToBlockDialogs() called without a top window?");
    return true;
  }

  topWindow = topWindow->GetCurrentInnerWindowInternal();
  if (!topWindow) {
    return true;
  }

  return topWindow->DialogsAreBeingAbused();
}

bool
nsGlobalWindow::AreDialogsEnabled()
{
  MOZ_ASSERT(IsOuterWindow());

  nsGlobalWindow *topWindow = GetScriptableTopInternal();
  if (!topWindow) {
    NS_ERROR("AreDialogsEnabled() called without a top window?");
    return false;
  }

  // TODO: Warn if no top window?
  topWindow = topWindow->GetCurrentInnerWindowInternal();
  if (!topWindow) {
    return false;
  }

  // Dialogs are blocked if the content viewer is hidden
  if (mDocShell) {
    nsCOMPtr<nsIContentViewer> cv;
    mDocShell->GetContentViewer(getter_AddRefs(cv));

    bool isHidden;
    cv->GetIsHidden(&isHidden);
    if (isHidden) {
      return false;
    }
  }

  // Dialogs are also blocked if the document is sandboxed with SANDBOXED_MODALS
  // (or if we have no document, of course).  Which document?  Who knows; the
  // spec is daft.  See <https://github.com/whatwg/html/issues/1206>.  For now
  // just go ahead and check mDoc, since in everything except edge cases in
  // which a frame is allow-same-origin but not allow-scripts and is being poked
  // at by some other window this should be the right thing anyway.
  if (!mDoc || (mDoc->GetSandboxFlags() & SANDBOXED_MODALS)) {
    return false;
  }

  return topWindow->mAreDialogsEnabled;
}

bool
nsGlobalWindow::DialogsAreBeingAbused()
{
  MOZ_ASSERT(IsInnerWindow());
  NS_ASSERTION(GetScriptableTopInternal() &&
               GetScriptableTopInternal()->GetCurrentInnerWindowInternal() == this,
               "DialogsAreBeingAbused called with invalid window");

  if (mLastDialogQuitTime.IsNull() ||
      nsContentUtils::IsCallerChrome()) {
    return false;
  }

  TimeDuration dialogInterval(TimeStamp::Now() - mLastDialogQuitTime);
  if (dialogInterval.ToSeconds() <
      Preferences::GetInt("dom.successive_dialog_time_limit",
                          DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT)) {
    mDialogAbuseCount++;

    return GetPopupControlState() > openAllowed ||
           mDialogAbuseCount > MAX_SUCCESSIVE_DIALOG_COUNT;
  }

  // Reset the abuse counter
  mDialogAbuseCount = 0;

  return false;
}

bool
nsGlobalWindow::ConfirmDialogIfNeeded()
{
  MOZ_ASSERT(IsOuterWindow());

  NS_ENSURE_TRUE(mDocShell, false);
  nsCOMPtr<nsIPromptService> promptSvc =
    do_GetService("@mozilla.org/embedcomp/prompt-service;1");

  if (!promptSvc) {
    return true;
  }

  // Reset popup state while opening a modal dialog, and firing events
  // about the dialog, to prevent the current state from being active
  // the whole time a modal dialog is open.
  nsAutoPopupStatePusher popupStatePusher(openAbused, true);

  bool disableDialog = false;
  nsXPIDLString label, title;
  nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
                                     "ScriptDialogLabel", label);
  nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
                                     "ScriptDialogPreventTitle", title);
  promptSvc->Confirm(AsOuter(), title.get(), label.get(), &disableDialog);
  if (disableDialog) {
    DisableDialogs();
    return false;
  }

  return true;
}

void
nsGlobalWindow::DisableDialogs()
{
  nsGlobalWindow *topWindow = GetScriptableTopInternal();
  if (!topWindow) {
    NS_ERROR("DisableDialogs() called without a top window?");
    return;
  }

  topWindow = topWindow->GetCurrentInnerWindowInternal();
  // TODO: Warn if no top window?
  if (topWindow) {
    topWindow->mAreDialogsEnabled = false;
  }
}

void
nsGlobalWindow::EnableDialogs()
{
  nsGlobalWindow *topWindow = GetScriptableTopInternal();
  if (!topWindow) {
    NS_ERROR("EnableDialogs() called without a top window?");
    return;
  }

  // TODO: Warn if no top window?
  topWindow = topWindow->GetCurrentInnerWindowInternal();
  if (topWindow) {
    topWindow->mAreDialogsEnabled = true;
  }
}

nsresult
nsGlobalWindow::PostHandleEvent(EventChainPostVisitor& aVisitor)
{
  NS_PRECONDITION(IsInnerWindow(), "PostHandleEvent is used on outer window!?");

  // Return early if there is nothing to do.
  switch (aVisitor.mEvent->mMessage) {
    case eResize:
    case eUnload:
    case eLoad:
      break;
    default:
      return NS_OK;
  }

  /* mChromeEventHandler and mContext go dangling in the middle of this
   function under some circumstances (events that destroy the window)
   without this addref. */
  nsCOMPtr<nsIDOMEventTarget> kungFuDeathGrip1(mChromeEventHandler);
  mozilla::Unused << kungFuDeathGrip1; // These aren't referred to through the function
  nsCOMPtr<nsIScriptContext> kungFuDeathGrip2(GetContextInternal());
  mozilla::Unused << kungFuDeathGrip2; // These aren't referred to through the function


  if (aVisitor.mEvent->mMessage == eResize) {
    mIsHandlingResizeEvent = false;
  } else if (aVisitor.mEvent->mMessage == eUnload &&
             aVisitor.mEvent->IsTrusted()) {
    // Execute bindingdetached handlers before we tear ourselves
    // down.
    if (mDoc) {
      mDoc->BindingManager()->ExecuteDetachedHandlers();
    }
    mIsDocumentLoaded = false;
  } else if (aVisitor.mEvent->mMessage == eLoad &&
             aVisitor.mEvent->IsTrusted()) {
    // This is page load event since load events don't propagate to |window|.
    // @see nsDocument::GetEventTargetParent.
    mIsDocumentLoaded = true;

    nsCOMPtr<Element> element = GetOuterWindow()->GetFrameElementInternal();
    nsIDocShell* docShell = GetDocShell();
    if (element && GetParentInternal() &&
        docShell && docShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
      // If we're not in chrome, or at a chrome boundary, fire the
      // onload event for the frame element.

      nsEventStatus status = nsEventStatus_eIgnore;
      WidgetEvent event(aVisitor.mEvent->IsTrusted(), eLoad);
      event.mFlags.mBubbles = false;
      event.mFlags.mCancelable = false;

      // Most of the time we could get a pres context to pass in here,
      // but not always (i.e. if this window is not shown there won't
      // be a pres context available). Since we're not firing a GUI
      // event we don't need a pres context anyway so we just pass
      // null as the pres context all the time here.
      EventDispatcher::Dispatch(element, nullptr, &event, nullptr, &status);
    }
  }

  return NS_OK;
}

nsresult
nsGlobalWindow::DispatchDOMEvent(WidgetEvent* aEvent,
                                 nsIDOMEvent* aDOMEvent,
                                 nsPresContext* aPresContext,
                                 nsEventStatus* aEventStatus)
{
  return EventDispatcher::DispatchDOMEvent(static_cast<nsPIDOMWindow*>(this),
                                           aEvent, aDOMEvent, aPresContext,
                                           aEventStatus);
}

void
nsGlobalWindow::PoisonOuterWindowProxy(JSObject *aObject)
{
  MOZ_ASSERT(IsOuterWindow());
  if (aObject == GetWrapperPreserveColor()) {
    PoisonWrapper();
  }
}

nsresult
nsGlobalWindow::SetArguments(nsIArray *aArguments)
{
  MOZ_ASSERT(IsOuterWindow());
  nsresult rv;

  // Historically, we've used the same machinery to handle openDialog arguments
  // (exposed via window.arguments) and showModalDialog arguments (exposed via
  // window.dialogArguments), even though the former is XUL-only and uses an XPCOM
  // array while the latter is web-exposed and uses an arbitrary JS value.
  // Moreover, per-spec |dialogArguments| is a property of the browsing context
  // (outer), whereas |arguments| lives on the inner.
  //
  // We've now mostly separated them, but the difference is still opaque to
  // nsWindowWatcher (the caller of SetArguments in this little back-and-forth
  // embedding waltz we do here).
  //
  // So we need to demultiplex the two cases here.
  nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal();
  if (mIsModalContentWindow) {
    // nsWindowWatcher blindly converts the original nsISupports into an array
    // of length 1. We need to recover it, and then cast it back to the concrete
    // object we know it to be.
    nsCOMPtr<nsISupports> supports = do_QueryElementAt(aArguments, 0, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    mDialogArguments = static_cast<DialogValueHolder*>(supports.get());
  } else {
    mArguments = aArguments;
    rv = currentInner->DefineArgumentsProperty(aArguments);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  return NS_OK;
}

nsresult
nsGlobalWindow::DefineArgumentsProperty(nsIArray *aArguments)
{
  MOZ_ASSERT(IsInnerWindow());
  MOZ_ASSERT(!mIsModalContentWindow); // Handled separately.

  nsIScriptContext *ctx = GetOuterWindowInternal()->mContext;
  NS_ENSURE_TRUE(aArguments && ctx, NS_ERROR_NOT_INITIALIZED);

  JS::Rooted<JSObject*> obj(RootingCx(), GetWrapperPreserveColor());
  return ctx->SetProperty(obj, "arguments", aArguments);
}

void
nsGlobalWindow::MaybeApplyBackPressure()
{
  MOZ_ASSERT(NS_IsMainThread());

  // If we are already suspended, then we don't need to apply back
  // pressure for ThrottledEventQueue reasons.  This also avoids repeatedly
  // calling SuspendTimeout() if this routine is executed many times
  // before dropping below the backpressure threshold.
  if (IsSuspended()) {
    return;
  }

  RefPtr<ThrottledEventQueue> taskQueue = TabGroup()->GetThrottledEventQueue();
  if (!taskQueue) {
    return;
  }

  // Only stop the window if it has greatly fallen behind the main thread.
  // This is a somewhat arbitrary threshold chosen such that it should
  // rarely fire under normaly circumstances.  Its low enough, though,
  // that we should avoid hitting an OOM from the backed up runnables in
  // the queue.
  static const uint32_t kThrottledEventQueueBackPressure = 5000;
  if (taskQueue->Length() < kThrottledEventQueueBackPressure) {
    return;
  }

  // First attempt to queue a runnable to resume running timeouts.  We do
  // this first in order to verify we can dispatch successfully.
  nsCOMPtr<nsIRunnable> r = NewRunnableMethod(this, &nsGlobalWindow::Resume);
  nsresult rv = taskQueue->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
  NS_ENSURE_SUCCESS_VOID(rv);

  // Since the resume is dispatched we can go ahead and suspend the window
  // now.  Once the task queue drains the resume will automatically get
  // executed balancing this suspend.
  // TODO: Consider suppressing event handling as well.
  Suspend();
}

//*****************************************************************************
// nsGlobalWindow::nsIScriptObjectPrincipal
//*****************************************************************************

nsIPrincipal*
nsGlobalWindow::GetPrincipal()
{
  if (mDoc) {
    // If we have a document, get the principal from the document
    return mDoc->NodePrincipal();
  }

  if (mDocumentPrincipal) {
    return mDocumentPrincipal;
  }

  // If we don't have a principal and we don't have a document we
  // ask the parent window for the principal. This can happen when
  // loading a frameset that has a <frame src="javascript:xxx">, in
  // that case the global window is used in JS before we've loaded
  // a document into the window.

  nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
    do_QueryInterface(GetParentInternal());

  if (objPrincipal) {
    return objPrincipal->GetPrincipal();
  }

  return nullptr;
}

//*****************************************************************************
// nsGlobalWindow::nsIDOMWindow
//*****************************************************************************

template <class T>
nsIURI*
nsPIDOMWindow<T>::GetDocumentURI() const
{
  return mDoc ? mDoc->GetDocumentURI() : mDocumentURI.get();
}

template <class T>
nsIURI*
nsPIDOMWindow<T>::GetDocBaseURI() const
{
  return mDoc ? mDoc->GetDocBaseURI() : mDocBaseURI.get();
}

template <class T>
void
nsPIDOMWindow<T>::MaybeCreateDoc()
{
  MOZ_ASSERT(!mDoc);
  if (nsIDocShell* docShell = GetDocShell()) {
    // Note that |document| here is the same thing as our mDoc, but we
    // don't have to explicitly set the member variable because the docshell
    // has already called SetNewDocument().
    nsCOMPtr<nsIDocument> document = docShell->GetDocument();
    Unused << document;
  }
}

void
nsPIDOMWindowOuter::SetInitialKeyboardIndicators(
  UIStateChangeType aShowAccelerators, UIStateChangeType aShowFocusRings)
{
  MOZ_ASSERT(IsOuterWindow());
  MOZ_ASSERT(!GetCurrentInnerWindow());

  nsPIDOMWindowOuter* piWin = GetPrivateRoot();
  if (!piWin) {
    return;
  }

  MOZ_ASSERT(piWin == AsOuter());

  // only change the flags that have been modified
  nsCOMPtr<nsPIWindowRoot> windowRoot = do_QueryInterface(mChromeEventHandler);
  if (!windowRoot) {
    return;
  }

  if (aShowAccelerators != UIStateChangeType_NoChange) {
    windowRoot->SetShowAccelerators(aShowAccelerators == UIStateChangeType_Set);
  }
  if (aShowFocusRings != UIStateChangeType_NoChange) {
    windowRoot->SetShowFocusRings(aShowFocusRings == UIStateChangeType_Set);
  }

  nsContentUtils::SetKeyboardIndicatorsOnRemoteChildren(GetOuterWindow(),
                                                        aShowAccelerators,
                                                        aShowFocusRings);
}

Element*
nsPIDOMWindowOuter::GetFrameElementInternal() const
{
  MOZ_ASSERT(IsOuterWindow());
  return mFrameElement;
}

void
nsPIDOMWindowOuter::SetFrameElementInternal(Element* aFrameElement)
{
  MOZ_ASSERT(IsOuterWindow());
  mFrameElement = aFrameElement;
}

bool
nsPIDOMWindowInner::AddAudioContext(AudioContext* aAudioContext)
{
  MOZ_ASSERT(IsInnerWindow());

  mAudioContexts.AppendElement(aAudioContext);

  // Return true if the context should be muted and false if not.
  nsIDocShell* docShell = GetDocShell();
  return docShell && !docShell->GetAllowMedia() && !aAudioContext->IsOffline();
}

void
nsPIDOMWindowInner::RemoveAudioContext(AudioContext* aAudioContext)
{
  MOZ_ASSERT(IsInnerWindow());

  mAudioContexts.RemoveElement(aAudioContext);
}

void
nsPIDOMWindowInner::MuteAudioContexts()
{
  MOZ_ASSERT(IsInnerWindow());

  for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
    if (!mAudioContexts[i]->IsOffline()) {
      mAudioContexts[i]->Mute();
    }
  }
}

void
nsPIDOMWindowInner::UnmuteAudioContexts()
{
  MOZ_ASSERT(IsInnerWindow());

  for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
    if (!mAudioContexts[i]->IsOffline()) {
      mAudioContexts[i]->Unmute();
    }
  }
}

nsGlobalWindow*
nsGlobalWindow::Window()
{
  return this;
}

nsGlobalWindow*
nsGlobalWindow::Self()
{
  return this;
}

Navigator*
nsGlobalWindow::GetNavigator(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());

  if (!mNavigator) {
    mNavigator = new Navigator(AsInner());
  }

  return mNavigator;
}

nsIDOMNavigator*
nsGlobalWindow::GetNavigator()
{
  FORWARD_TO_INNER(GetNavigator, (), nullptr);

  ErrorResult dummy;
  nsIDOMNavigator* navigator = GetNavigator(dummy);
  dummy.SuppressException();
  return navigator;
}

nsScreen*
nsGlobalWindow::GetScreen(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());

  if (!mScreen) {
    mScreen = nsScreen::Create(AsInner());
    if (!mScreen) {
      aError.Throw(NS_ERROR_UNEXPECTED);
      return nullptr;
    }
  }

  return mScreen;
}

nsIDOMScreen*
nsGlobalWindow::GetScreen()
{
  FORWARD_TO_INNER(GetScreen, (), nullptr);

  ErrorResult dummy;
  nsIDOMScreen* screen = GetScreen(dummy);
  dummy.SuppressException();
  return screen;
}

nsHistory*
nsGlobalWindow::GetHistory(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());

  if (!mHistory) {
    mHistory = new nsHistory(AsInner());
  }

  return mHistory;
}

CustomElementRegistry*
nsGlobalWindow::CustomElements()
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());
  if (!mCustomElements) {
      mCustomElements = CustomElementRegistry::Create(AsInner());
  }

  return mCustomElements;
}

Performance*
nsPIDOMWindowInner::GetPerformance()
{
  MOZ_ASSERT(IsInnerWindow());
  CreatePerformanceObjectIfNeeded();
  return mPerformance;
}

Performance*
nsGlobalWindow::GetPerformance()
{
  return AsInner()->GetPerformance();
}

void
nsPIDOMWindowInner::CreatePerformanceObjectIfNeeded()
{
  MOZ_ASSERT(IsInnerWindow());

  if (mPerformance || !mDoc) {
    return;
  }
  RefPtr<nsDOMNavigationTiming> timing = mDoc->GetNavigationTiming();
  nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(mDoc->GetChannel()));
  bool timingEnabled = false;
  if (!timedChannel ||
      !NS_SUCCEEDED(timedChannel->GetTimingEnabled(&timingEnabled)) ||
      !timingEnabled) {
    timedChannel = nullptr;
  }
  if (timing) {
    // If we are dealing with an iframe, we will need the parent's performance
    // object (so we can add the iframe as a resource of that page).
    Performance* parentPerformance = nullptr;
    nsCOMPtr<nsPIDOMWindowOuter> parentWindow = GetScriptableParentOrNull();
    if (parentWindow) {
      nsPIDOMWindowInner* parentInnerWindow = nullptr;
      if (parentWindow) {
        parentInnerWindow = parentWindow->GetCurrentInnerWindow();
      }
      if (parentInnerWindow) {
        parentPerformance = parentInnerWindow->GetPerformance();
      }
    }
    mPerformance =
      Performance::CreateForMainThread(this, timing, timedChannel,
                                       parentPerformance);
  }
}

bool
nsPIDOMWindowInner::IsSecureContext() const
{
  return nsGlobalWindow::Cast(this)->IsSecureContext();
}

void
nsPIDOMWindowInner::Suspend()
{
  nsGlobalWindow::Cast(this)->Suspend();
}

void
nsPIDOMWindowInner::Resume()
{
  nsGlobalWindow::Cast(this)->Resume();
}

void
nsPIDOMWindowInner::Freeze()
{
  nsGlobalWindow::Cast(this)->Freeze();
}

void
nsPIDOMWindowInner::Thaw()
{
  nsGlobalWindow::Cast(this)->Thaw();
}

void
nsPIDOMWindowInner::SyncStateFromParentWindow()
{
  nsGlobalWindow::Cast(this)->SyncStateFromParentWindow();
}

SuspendTypes
nsPIDOMWindowOuter::GetMediaSuspend() const
{
  if (IsInnerWindow()) {
    return mOuterWindow->GetMediaSuspend();
  }

  return mMediaSuspend;
}

void
nsPIDOMWindowOuter::SetMediaSuspend(SuspendTypes aSuspend)
{
  if (IsInnerWindow()) {
    mOuterWindow->SetMediaSuspend(aSuspend);
    return;
  }

  if (!IsDisposableSuspend(aSuspend)) {
    mMediaSuspend = aSuspend;
  }

  RefreshMediaElementsSuspend(aSuspend);
}

bool
nsPIDOMWindowOuter::GetAudioMuted() const
{
  if (IsInnerWindow()) {
    return mOuterWindow->GetAudioMuted();
  }

  return mAudioMuted;
}

void
nsPIDOMWindowOuter::SetAudioMuted(bool aMuted)
{
  if (IsInnerWindow()) {
    mOuterWindow->SetAudioMuted(aMuted);
    return;
  }

  if (mAudioMuted == aMuted) {
    return;
  }

  mAudioMuted = aMuted;
  RefreshMediaElementsVolume();
}

float
nsPIDOMWindowOuter::GetAudioVolume() const
{
  if (IsInnerWindow()) {
    return mOuterWindow->GetAudioVolume();
  }

  return mAudioVolume;
}

nsresult
nsPIDOMWindowOuter::SetAudioVolume(float aVolume)
{
  if (IsInnerWindow()) {
    return mOuterWindow->SetAudioVolume(aVolume);
  }

  if (aVolume < 0.0) {
    return NS_ERROR_DOM_INDEX_SIZE_ERR;
  }

  if (mAudioVolume == aVolume) {
    return NS_OK;
  }

  mAudioVolume = aVolume;
  RefreshMediaElementsVolume();
  return NS_OK;
}

void
nsPIDOMWindowOuter::RefreshMediaElementsVolume()
{
  RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
  if (service) {
    service->RefreshAgentsVolume(GetOuterWindow());
  }
}

void
nsPIDOMWindowOuter::RefreshMediaElementsSuspend(SuspendTypes aSuspend)
{
  RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
  if (service) {
    service->RefreshAgentsSuspend(GetOuterWindow(), aSuspend);
  }
}

bool
nsPIDOMWindowOuter::IsDisposableSuspend(SuspendTypes aSuspend) const
{
  return (aSuspend == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE ||
          aSuspend == nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE);
}

void
nsPIDOMWindowOuter::SetServiceWorkersTestingEnabled(bool aEnabled)
{
  // Devtools should only be setting this on the top level window.  Its
  // ok if devtools clears the flag on clean up of nested windows, though.
  // It will have no affect.
#ifdef DEBUG
  nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetScriptableTop();
  MOZ_ASSERT_IF(aEnabled, this == topWindow);
#endif
  mServiceWorkersTestingEnabled = aEnabled;
}

bool
nsPIDOMWindowOuter::GetServiceWorkersTestingEnabled()
{
  // Automatically get this setting from the top level window so that nested
  // iframes get the correct devtools setting.
  nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetScriptableTop();
  if (!topWindow) {
    return false;
  }
  return topWindow->mServiceWorkersTestingEnabled;
}

bool
nsPIDOMWindowInner::GetAudioCaptured() const
{
  MOZ_ASSERT(IsInnerWindow());
  return mAudioCaptured;
}

nsresult
nsPIDOMWindowInner::SetAudioCapture(bool aCapture)
{
  MOZ_ASSERT(IsInnerWindow());

  mAudioCaptured = aCapture;

  RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
  if (service) {
    service->SetWindowAudioCaptured(GetOuterWindow(), mWindowID, aCapture);
  }

  return NS_OK;
}

// nsISpeechSynthesisGetter

#ifdef MOZ_WEBSPEECH
SpeechSynthesis*
nsGlobalWindow::GetSpeechSynthesis(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());

  if (!mSpeechSynthesis) {
    mSpeechSynthesis = new SpeechSynthesis(AsInner());
  }

  return mSpeechSynthesis;
}

bool
nsGlobalWindow::HasActiveSpeechSynthesis()
{
  MOZ_ASSERT(IsInnerWindow());

  if (mSpeechSynthesis) {
    return !mSpeechSynthesis->HasEmptyQueue();
  }

  return false;
}

#endif

already_AddRefed<nsPIDOMWindowOuter>
nsGlobalWindow::GetParentOuter()
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  if (!mDocShell) {
    return nullptr;
  }

  nsCOMPtr<nsPIDOMWindowOuter> parent;
  if (mDocShell->GetIsMozBrowser()) {
    parent = AsOuter();
  } else {
    parent = GetParent();
  }

  return parent.forget();
}

already_AddRefed<nsPIDOMWindowOuter>
nsGlobalWindow::GetParent(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetParentOuter, (), aError, nullptr);
}

/**
 * GetScriptableParent is called when script reads window.parent.
 *
 * In contrast to GetRealParent, GetScriptableParent respects <iframe
 * mozbrowser> boundaries, so if |this| is contained by an <iframe
 * mozbrowser>, we will return |this| as its own parent.
 */
nsPIDOMWindowOuter*
nsGlobalWindow::GetScriptableParent()
{
  FORWARD_TO_OUTER(GetScriptableParent, (), nullptr);

  nsCOMPtr<nsPIDOMWindowOuter> parent = GetParentOuter();
  return parent.get();
}

/**
 * Behavies identically to GetScriptableParent extept that it returns null
 * if GetScriptableParent would return this window.
 */
nsPIDOMWindowOuter*
nsGlobalWindow::GetScriptableParentOrNull()
{
  FORWARD_TO_OUTER(GetScriptableParentOrNull, (), nullptr);

  nsPIDOMWindowOuter* parent = GetScriptableParent();
  return (Cast(parent) == this) ? nullptr : parent;
}

/**
 * nsPIDOMWindow::GetParent (when called from C++) is just a wrapper around
 * GetRealParent.
 */
already_AddRefed<nsPIDOMWindowOuter>
nsGlobalWindow::GetParent()
{
  MOZ_ASSERT(IsOuterWindow());

  if (!mDocShell) {
    return nullptr;
  }

  nsCOMPtr<nsIDocShell> parent;
  mDocShell->GetSameTypeParentIgnoreBrowserBoundaries(getter_AddRefs(parent));

  if (parent) {
    nsCOMPtr<nsPIDOMWindowOuter> win = parent->GetWindow();
    return win.forget();
  }

  nsCOMPtr<nsPIDOMWindowOuter> win(AsOuter());
  return win.forget();
}

static nsresult
GetTopImpl(nsGlobalWindow* aWin, nsPIDOMWindowOuter** aTop, bool aScriptable)
{
  *aTop = nullptr;

  // Walk up the parent chain.

  nsCOMPtr<nsPIDOMWindowOuter> prevParent = aWin->AsOuter();
  nsCOMPtr<nsPIDOMWindowOuter> parent = aWin->AsOuter();
  do {
    if (!parent) {
      break;
    }

    prevParent = parent;

    nsCOMPtr<nsPIDOMWindowOuter> newParent;
    if (aScriptable) {
      newParent = parent->GetScriptableParent();
    }
    else {
      newParent = parent->GetParent();
    }

    parent = newParent;

  } while (parent != prevParent);

  if (parent) {
    parent.swap(*aTop);
  }

  return NS_OK;
}

/**
 * GetScriptableTop is called when script reads window.top.
 *
 * In contrast to GetRealTop, GetScriptableTop respects <iframe mozbrowser>
 * boundaries.  If we encounter a window owned by an <iframe mozbrowser> while
 * walking up the window hierarchy, we'll stop and return that window.
 */
nsPIDOMWindowOuter*
nsGlobalWindow::GetScriptableTop()
{
  FORWARD_TO_OUTER(GetScriptableTop, (), nullptr);
  nsCOMPtr<nsPIDOMWindowOuter> window;
  GetTopImpl(this, getter_AddRefs(window), /* aScriptable = */ true);
  return window.get();
}

already_AddRefed<nsPIDOMWindowOuter>
nsGlobalWindow::GetTop()
{
  MOZ_ASSERT(IsOuterWindow());
  nsCOMPtr<nsPIDOMWindowOuter> window;
  GetTopImpl(this, getter_AddRefs(window), /* aScriptable = */ false);
  return window.forget();
}

void
nsGlobalWindow::GetContentOuter(JSContext* aCx,
                                JS::MutableHandle<JSObject*> aRetval,
                                ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  nsCOMPtr<nsPIDOMWindowOuter> content =
    GetContentInternal(aError, !nsContentUtils::IsCallerChrome());
  if (aError.Failed()) {
    return;
  }

  if (content) {
    JS::Rooted<JS::Value> val(aCx);
    aError = nsContentUtils::WrapNative(aCx, content, &val);
    if (aError.Failed()) {
      return;
    }

    aRetval.set(&val.toObject());
    return;
  }

  aRetval.set(nullptr);
  return;
}

void
nsGlobalWindow::GetContent(JSContext* aCx,
                           JS::MutableHandle<JSObject*> aRetval,
                           ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetContentOuter, (aCx, aRetval, aError), aError, );
}

already_AddRefed<nsPIDOMWindowOuter>
nsGlobalWindow::GetContentInternal(ErrorResult& aError, bool aUnprivilegedCaller)
{
  MOZ_ASSERT(IsOuterWindow());

  // First check for a named frame named "content"
  nsCOMPtr<nsPIDOMWindowOuter> domWindow =
    GetChildWindow(NS_LITERAL_STRING("content"));
  if (domWindow) {
    return domWindow.forget();
  }

  // If we're contained in <iframe mozbrowser>, then GetContent is the same as
  // window.top.
  if (mDocShell && mDocShell->GetIsInMozBrowser()) {
    return GetTopOuter();
  }

  nsCOMPtr<nsIDocShellTreeItem> primaryContent;
  if (aUnprivilegedCaller) {
    // If we're called by non-chrome code, make sure we don't return
    // the primary content window if the calling tab is hidden. In
    // such a case we return the same-type root in the hidden tab,
    // which is "good enough", for now.
    nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(mDocShell));

    if (baseWin) {
      bool visible = false;
      baseWin->GetVisibility(&visible);

      if (!visible) {
        mDocShell->GetSameTypeRootTreeItem(getter_AddRefs(primaryContent));
      }
    }
  }

  if (!primaryContent) {
    nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
    if (!treeOwner) {
      aError.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }

    treeOwner->GetPrimaryContentShell(getter_AddRefs(primaryContent));
  }

  if (!primaryContent) {
    return nullptr;
  }

  domWindow = primaryContent->GetWindow();
  return domWindow.forget();
}

MozSelfSupport*
nsGlobalWindow::GetMozSelfSupport(ErrorResult& aError)
{
  MOZ_ASSERT(IsInnerWindow());

  if (mMozSelfSupport) {
    return mMozSelfSupport;
  }

  // We're called from JS and want to use out existing JSContext (and,
  // importantly, its compartment!) here.
  AutoJSContext cx;
  GlobalObject global(cx, FastGetGlobalJSObject());
  mMozSelfSupport = MozSelfSupport::Constructor(global, cx, aError);
  return mMozSelfSupport;
}

nsresult
nsGlobalWindow::GetScriptableContent(JSContext* aCx, JS::MutableHandle<JS::Value> aVal)
{
  ErrorResult rv;
  JS::Rooted<JSObject*> content(aCx);
  GetContent(aCx, &content, rv);
  if (!rv.Failed()) {
    aVal.setObjectOrNull(content);
  }

  return rv.StealNSResult();
}

nsresult
nsGlobalWindow::GetPrompter(nsIPrompt** aPrompt)
{
  if (IsInnerWindow()) {
    nsGlobalWindow* outer = GetOuterWindowInternal();
    if (!outer) {
      NS_WARNING("No outer window available!");
      return NS_ERROR_NOT_INITIALIZED;
    }
    return outer->GetPrompter(aPrompt);
  }

  if (!mDocShell)
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsIPrompt> prompter(do_GetInterface(mDocShell));
  NS_ENSURE_TRUE(prompter, NS_ERROR_NO_INTERFACE);

  prompter.forget(aPrompt);
  return NS_OK;
}

BarProp*
nsGlobalWindow::GetMenubar(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());

  if (!mMenubar) {
    mMenubar = new MenubarProp(this);
  }

  return mMenubar;
}

BarProp*
nsGlobalWindow::GetToolbar(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());

  if (!mToolbar) {
    mToolbar = new ToolbarProp(this);
  }

  return mToolbar;
}

BarProp*
nsGlobalWindow::GetLocationbar(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());

  if (!mLocationbar) {
    mLocationbar = new LocationbarProp(this);
  }
  return mLocationbar;
}

BarProp*
nsGlobalWindow::GetPersonalbar(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());

  if (!mPersonalbar) {
    mPersonalbar = new PersonalbarProp(this);
  }
  return mPersonalbar;
}

BarProp*
nsGlobalWindow::GetStatusbar(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());

  if (!mStatusbar) {
    mStatusbar = new StatusbarProp(this);
  }
  return mStatusbar;
}

BarProp*
nsGlobalWindow::GetScrollbars(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());

  if (!mScrollbars) {
    mScrollbars = new ScrollbarsProp(this);
  }

  return mScrollbars;
}

bool
nsGlobalWindow::GetClosedOuter()
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  // If someone called close(), or if we don't have a docshell, we're closed.
  return mIsClosed || !mDocShell;
}

bool
nsGlobalWindow::GetClosed(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetClosedOuter, (), aError, false);
}

bool
nsGlobalWindow::Closed()
{
  MOZ_ASSERT(IsOuterWindow());

  return GetClosedOuter();
}

nsDOMWindowList*
nsGlobalWindow::GetWindowList()
{
  MOZ_ASSERT(IsOuterWindow());

  if (!mFrames && mDocShell) {
    mFrames = new nsDOMWindowList(mDocShell);
  }

  return mFrames;
}

already_AddRefed<nsIDOMWindowCollection>
nsGlobalWindow::GetFrames()
{
  FORWARD_TO_OUTER(GetFrames, (), nullptr);

  nsCOMPtr<nsIDOMWindowCollection> frames = GetWindowList();
  return frames.forget();
}

already_AddRefed<nsPIDOMWindowOuter>
nsGlobalWindow::IndexedGetterOuter(uint32_t aIndex)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  nsDOMWindowList* windows = GetWindowList();
  NS_ENSURE_TRUE(windows, nullptr);

  return windows->IndexedGetter(aIndex);
}

already_AddRefed<nsPIDOMWindowOuter>
nsGlobalWindow::IndexedGetter(uint32_t aIndex)
{
  FORWARD_TO_OUTER(IndexedGetterOuter, (aIndex), nullptr);
  MOZ_CRASH();
}

bool
nsGlobalWindow::DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObj,
                          JS::Handle<jsid> aId,
                          JS::MutableHandle<JS::PropertyDescriptor> aDesc)
{
  MOZ_ASSERT(IsInnerWindow());

  // Note: Keep this in sync with MayResolve.

  // Note: The infallibleInit call in GlobalResolve depends on this check.
  if (!JSID_IS_STRING(aId)) {
    return true;
  }

  bool found;
  if (!WebIDLGlobalNameHash::DefineIfEnabled(aCx, aObj, aId, aDesc, &found)) {
    return false;
  }

  if (found) {
    return true;
  }

  nsresult rv = nsWindowSH::GlobalResolve(this, aCx, aObj, aId, aDesc);
  if (NS_FAILED(rv)) {
    return Throw(aCx, rv);
  }

  return true;
}

/* static */
bool
nsGlobalWindow::MayResolve(jsid aId)
{
  // Note: This function does not fail and may not have any side-effects.
  // Note: Keep this in sync with DoResolve.
  if (!JSID_IS_STRING(aId)) {
    return false;
  }

  if (aId == XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS)) {
    return true;
  }

  if (aId == XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_CONTROLLERS)) {
    // We only resolve .controllers in release builds and on non-chrome windows,
    // but let's not worry about any of that stuff.
    return true;
  }

  if (WebIDLGlobalNameHash::MayResolve(aId)) {
    return true;
  }

  nsScriptNameSpaceManager *nameSpaceManager = PeekNameSpaceManager();
  if (!nameSpaceManager) {
    // Really shouldn't happen.  Fail safe.
    return true;
  }

  nsAutoString name;
  AssignJSFlatString(name, JSID_TO_FLAT_STRING(aId));

  return nameSpaceManager->LookupName(name);
}

void
nsGlobalWindow::GetOwnPropertyNames(JSContext* aCx, nsTArray<nsString>& aNames,
                                    ErrorResult& aRv)
{
  MOZ_ASSERT(IsInnerWindow());
  // "Components" is marked as enumerable but only resolved on demand :-/.
  //aNames.AppendElement(NS_LITERAL_STRING("Components"));

  nsScriptNameSpaceManager* nameSpaceManager = GetNameSpaceManager();
  if (nameSpaceManager) {
    JS::Rooted<JSObject*> wrapper(aCx, GetWrapper());

    WebIDLGlobalNameHash::GetNames(aCx, wrapper, aNames);

    for (auto i = nameSpaceManager->GlobalNameIter(); !i.Done(); i.Next()) {
      const GlobalNameMapEntry* entry = i.Get();
      if (nsWindowSH::NameStructEnabled(aCx, this, entry->mKey,
                                        entry->mGlobalName)) {
        aNames.AppendElement(entry->mKey);
      }
    }
  }
}

/* static */ bool
nsGlobalWindow::IsPrivilegedChromeWindow(JSContext* aCx, JSObject* aObj)
{
  // For now, have to deal with XPConnect objects here.
  return xpc::WindowOrNull(aObj)->IsChromeWindow() &&
         nsContentUtils::ObjectPrincipal(aObj) == nsContentUtils::GetSystemPrincipal();
}

/* static */ bool
nsGlobalWindow::IsShowModalDialogEnabled(JSContext*, JSObject*)
{
  static bool sAddedPrefCache = false;
  static bool sIsDisabled;
  static const char sShowModalDialogPref[] = "dom.disable_window_showModalDialog";

  if (!sAddedPrefCache) {
    Preferences::AddBoolVarCache(&sIsDisabled, sShowModalDialogPref, false);
    sAddedPrefCache = true;
  }

  return !sIsDisabled && !XRE_IsContentProcess();
}

nsIDOMOfflineResourceList*
nsGlobalWindow::GetApplicationCache(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());

  if (!mApplicationCache) {
    nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(GetDocShell()));
    if (!webNav || !mDoc) {
      aError.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }

    nsCOMPtr<nsIURI> uri;
    aError = webNav->GetCurrentURI(getter_AddRefs(uri));
    if (aError.Failed()) {
      return nullptr;
    }

    nsCOMPtr<nsIURI> manifestURI;
    nsContentUtils::GetOfflineAppManifest(mDoc, getter_AddRefs(manifestURI));

    RefPtr<nsDOMOfflineResourceList> applicationCache =
      new nsDOMOfflineResourceList(manifestURI, uri, mDoc->NodePrincipal(),
                                   AsInner());

    applicationCache->Init();

    mApplicationCache = applicationCache;
  }

  return mApplicationCache;
}

already_AddRefed<nsIDOMOfflineResourceList>
nsGlobalWindow::GetApplicationCache()
{
  FORWARD_TO_INNER(GetApplicationCache, (), nullptr);

  ErrorResult dummy;
  nsCOMPtr<nsIDOMOfflineResourceList> applicationCache =
    GetApplicationCache(dummy);
  dummy.SuppressException();
  return applicationCache.forget();
}

Crypto*
nsGlobalWindow::GetCrypto(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());

  if (!mCrypto) {
    mCrypto = new Crypto();
    mCrypto->Init(this);
  }
  return mCrypto;
}

mozilla::dom::U2F*
nsGlobalWindow::GetU2f(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());

  if (!mU2F) {
    RefPtr<U2F> u2f = new U2F();
    u2f->Init(AsInner(), aError);
    if (NS_WARN_IF(aError.Failed())) {
      return nullptr;
    }

    mU2F = u2f;
  }
  return mU2F;
}

nsIControllers*
nsGlobalWindow::GetControllersOuter(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  if (!mControllers) {
    nsresult rv;
    mControllers = do_CreateInstance(kXULControllersCID, &rv);
    if (NS_FAILED(rv)) {
      aError.Throw(rv);
      return nullptr;
    }

    // Add in the default controller
    nsCOMPtr<nsIController> controller = do_CreateInstance(
                               NS_WINDOWCONTROLLER_CONTRACTID, &rv);
    if (NS_FAILED(rv)) {
      aError.Throw(rv);
      return nullptr;
    }

    mControllers->InsertControllerAt(0, controller);
    nsCOMPtr<nsIControllerContext> controllerContext = do_QueryInterface(controller);
    if (!controllerContext) {
      aError.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }

    controllerContext->SetCommandContext(static_cast<nsIDOMWindow*>(this));
  }

  return mControllers;
}

nsIControllers*
nsGlobalWindow::GetControllers(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetControllersOuter, (aError), aError, nullptr);
}

nsresult
nsGlobalWindow::GetControllers(nsIControllers** aResult)
{
  FORWARD_TO_INNER(GetControllers, (aResult), NS_ERROR_UNEXPECTED);

  ErrorResult rv;
  nsCOMPtr<nsIControllers> controllers = GetControllers(rv);
  controllers.forget(aResult);

  return rv.StealNSResult();
}

nsPIDOMWindowOuter*
nsGlobalWindow::GetSanitizedOpener(nsPIDOMWindowOuter* aOpener)
{
  if (!aOpener) {
    return nullptr;
  }

  nsGlobalWindow* win = nsGlobalWindow::Cast(aOpener);

  // First, ensure that we're not handing back a chrome window to content:
  if (win->IsChromeWindow()) {
    return nullptr;
  }

  // We don't want to reveal the opener if the opener is a mail window,
  // because opener can be used to spoof the contents of a message (bug 105050).
  // So, we look in the opener's root docshell to see if it's a mail window.
  nsCOMPtr<nsIDocShell> openerDocShell = aOpener->GetDocShell();

  if (openerDocShell) {
    nsCOMPtr<nsIDocShellTreeItem> openerRootItem;
    openerDocShell->GetRootTreeItem(getter_AddRefs(openerRootItem));
    nsCOMPtr<nsIDocShell> openerRootDocShell(do_QueryInterface(openerRootItem));
    if (openerRootDocShell) {
      uint32_t appType;
      nsresult rv = openerRootDocShell->GetAppType(&appType);
      if (NS_SUCCEEDED(rv) && appType != nsIDocShell::APP_TYPE_MAIL) {
        return aOpener;
      }
    }
  }

  return nullptr;
}

nsPIDOMWindowOuter*
nsGlobalWindow::GetOpenerWindowOuter()
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  nsCOMPtr<nsPIDOMWindowOuter> opener = do_QueryReferent(mOpener);

  if (!opener) {
    return nullptr;
  }

  // First, check if we were called from a privileged chrome script
  if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
    // Catch the case where we're chrome but the opener is not...
    if (GetPrincipal() == nsContentUtils::GetSystemPrincipal() &&
        nsGlobalWindow::Cast(opener)->GetPrincipal() != nsContentUtils::GetSystemPrincipal()) {
      return nullptr;
    }
    return opener;
  }

  return GetSanitizedOpener(opener);
}

nsPIDOMWindowOuter*
nsGlobalWindow::GetOpenerWindow(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetOpenerWindowOuter, (), aError, nullptr);
}

void
nsGlobalWindow::GetOpener(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval,
                          ErrorResult& aError)
{
  MOZ_ASSERT(IsInnerWindow());

  nsCOMPtr<nsPIDOMWindowOuter> opener = GetOpenerWindow(aError);
  if (aError.Failed() || !opener) {
    aRetval.setNull();
    return;
  }

  aError = nsContentUtils::WrapNative(aCx, opener, aRetval);
}

already_AddRefed<nsPIDOMWindowOuter>
nsGlobalWindow::GetOpener()
{
  FORWARD_TO_OUTER(GetOpener, (), nullptr);

  nsCOMPtr<nsPIDOMWindowOuter> opener = GetOpenerWindowOuter();
  return opener.forget();
}

void
nsGlobalWindow::SetOpener(JSContext* aCx, JS::Handle<JS::Value> aOpener,
                          ErrorResult& aError)
{
  // Check if we were called from a privileged chrome script.  If not, and if
  // aOpener is not null, just define aOpener on our inner window's JS object,
  // wrapped into the current compartment so that for Xrays we define on the
  // Xray expando object, but don't set it on the outer window, so that it'll
  // get reset on navigation.  This is just like replaceable properties, but
  // we're not quite readonly.
  if (!aOpener.isNull() && !nsContentUtils::IsCallerChrome()) {
    RedefineProperty(aCx, "opener", aOpener, aError);
    return;
  }

  if (!aOpener.isObjectOrNull()) {
    // Chrome code trying to set some random value as opener
    aError.Throw(NS_ERROR_INVALID_ARG);
    return;
  }

  nsPIDOMWindowInner* win = nullptr;
  if (aOpener.isObject()) {
    JSObject* unwrapped = js::CheckedUnwrap(&aOpener.toObject(),
                                            /* stopAtWindowProxy = */ false);
    if (!unwrapped) {
      aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
      return;
    }

    auto* globalWindow = xpc::WindowOrNull(unwrapped);
    if (!globalWindow) {
      // Wasn't a window
      aError.Throw(NS_ERROR_INVALID_ARG);
      return;
    }

    win = globalWindow->AsInner();
  }

  nsPIDOMWindowOuter* outer = nullptr;
  if (win) {
    if (!win->IsCurrentInnerWindow()) {
      aError.Throw(NS_ERROR_FAILURE);
      return;
    }
    outer = win->GetOuterWindow();
  }

  SetOpenerWindow(outer, false);
}

void
nsGlobalWindow::GetStatusOuter(nsAString& aStatus)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  aStatus = mStatus;
}

void
nsGlobalWindow::GetStatus(nsAString& aStatus, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetStatusOuter, (aStatus), aError, );
}

void
nsGlobalWindow::SetStatusOuter(const nsAString& aStatus)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  mStatus = aStatus;

  /*
   * If caller is not chrome and dom.disable_window_status_change is true,
   * prevent propagating window.status to the UI by exiting early
   */

  if (!CanSetProperty("dom.disable_window_status_change")) {
    return;
  }

  nsCOMPtr<nsIWebBrowserChrome> browserChrome = GetWebBrowserChrome();
  if (browserChrome) {
    browserChrome->SetStatus(nsIWebBrowserChrome::STATUS_SCRIPT,
                             PromiseFlatString(aStatus).get());
  }
}

void
nsGlobalWindow::SetStatus(const nsAString& aStatus, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetStatusOuter, (aStatus), aError, );
}

void
nsGlobalWindow::GetNameOuter(nsAString& aName)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  if (mDocShell) {
    mDocShell->GetName(aName);
  }
}

void
nsGlobalWindow::GetName(nsAString& aName, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetNameOuter, (aName), aError, );
}

void
nsGlobalWindow::SetNameOuter(const nsAString& aName, mozilla::ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  if (mDocShell) {
    aError = mDocShell->SetName(aName);
  }
}

void
nsGlobalWindow::SetName(const nsAString& aName, mozilla::ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetNameOuter, (aName, aError), aError, );
}

// Helper functions used by many methods below.
int32_t
nsGlobalWindow::DevToCSSIntPixels(int32_t px)
{
  if (!mDocShell)
    return px; // assume 1:1

  RefPtr<nsPresContext> presContext;
  mDocShell->GetPresContext(getter_AddRefs(presContext));
  if (!presContext)
    return px;

  return presContext->DevPixelsToIntCSSPixels(px);
}

int32_t
nsGlobalWindow::CSSToDevIntPixels(int32_t px)
{
  if (!mDocShell)
    return px; // assume 1:1

  RefPtr<nsPresContext> presContext;
  mDocShell->GetPresContext(getter_AddRefs(presContext));
  if (!presContext)
    return px;

  return presContext->CSSPixelsToDevPixels(px);
}

nsIntSize
nsGlobalWindow::DevToCSSIntPixels(nsIntSize px)
{
  if (!mDocShell)
    return px; // assume 1:1

  RefPtr<nsPresContext> presContext;
  mDocShell->GetPresContext(getter_AddRefs(presContext));
  if (!presContext)
    return px;

  return nsIntSize(
      presContext->DevPixelsToIntCSSPixels(px.width),
      presContext->DevPixelsToIntCSSPixels(px.height));
}

nsIntSize
nsGlobalWindow::CSSToDevIntPixels(nsIntSize px)
{
  if (!mDocShell)
    return px; // assume 1:1

  RefPtr<nsPresContext> presContext;
  mDocShell->GetPresContext(getter_AddRefs(presContext));
  if (!presContext)
    return px;

  return nsIntSize(
    presContext->CSSPixelsToDevPixels(px.width),
    presContext->CSSPixelsToDevPixels(px.height));
}

nsresult
nsGlobalWindow::GetInnerSize(CSSIntSize& aSize)
{
  MOZ_ASSERT(IsOuterWindow());

  EnsureSizeUpToDate();

  NS_ENSURE_STATE(mDocShell);

  RefPtr<nsPresContext> presContext;
  mDocShell->GetPresContext(getter_AddRefs(presContext));
  RefPtr<nsIPresShell> presShell = mDocShell->GetPresShell();

  if (!presContext || !presShell) {
    aSize = CSSIntSize(0, 0);
    return NS_OK;
  }

  /*
   * On platforms with resolution-based zooming, the CSS viewport
   * and visual viewport may not be the same. The inner size should
   * be the visual viewport, but we fall back to the CSS viewport
   * if it is not set.
   */
  if (presShell->IsScrollPositionClampingScrollPortSizeSet()) {
    aSize = CSSIntRect::FromAppUnitsRounded(
      presShell->GetScrollPositionClampingScrollPortSize());
  } else {
    RefPtr<nsViewManager> viewManager = presShell->GetViewManager();
    if (viewManager) {
      viewManager->FlushDelayedResize(false);
    }

    aSize = CSSIntRect::FromAppUnitsRounded(
      presContext->GetVisibleArea().Size());
  }
  return NS_OK;
}

int32_t
nsGlobalWindow::GetInnerWidthOuter(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  CSSIntSize size;
  aError = GetInnerSize(size);
  return size.width;
}

int32_t
nsGlobalWindow::GetInnerWidth(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetInnerWidthOuter, (aError), aError, 0);
}

void
nsGlobalWindow::GetInnerWidth(JSContext* aCx,
                              JS::MutableHandle<JS::Value> aValue,
                              ErrorResult& aError)
{
  GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetInnerWidth, aValue,
                            aError);
}

nsresult
nsGlobalWindow::GetInnerWidth(int32_t* aInnerWidth)
{
  FORWARD_TO_INNER(GetInnerWidth, (aInnerWidth), NS_ERROR_UNEXPECTED);

  ErrorResult rv;
  *aInnerWidth = GetInnerWidth(rv);

  return rv.StealNSResult();
}

void
nsGlobalWindow::SetInnerWidthOuter(int32_t aInnerWidth, ErrorResult& aError, bool aCallerIsChrome)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  if (!mDocShell) {
    aError.Throw(NS_ERROR_UNEXPECTED);
    return;
  }

  CheckSecurityWidthAndHeight(&aInnerWidth, nullptr, aCallerIsChrome);

  RefPtr<nsIPresShell> presShell = mDocShell->GetPresShell();

  if (presShell && presShell->GetIsViewportOverridden())
  {
    nscoord height = 0;

    RefPtr<nsPresContext> presContext;
    presContext = presShell->GetPresContext();

    nsRect shellArea = presContext->GetVisibleArea();
    height = shellArea.height;
    SetCSSViewportWidthAndHeight(nsPresContext::CSSPixelsToAppUnits(aInnerWidth),
                                 height);
    return;
  }

  int32_t height = 0;
  int32_t unused  = 0;

  nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(mDocShell));
  docShellAsWin->GetSize(&unused, &height);
  aError = SetDocShellWidthAndHeight(CSSToDevIntPixels(aInnerWidth), height);
}

void
nsGlobalWindow::SetInnerWidth(int32_t aInnerWidth, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetInnerWidthOuter, (aInnerWidth, aError, nsContentUtils::IsCallerChrome()), aError, );
}

void
nsGlobalWindow::SetInnerWidth(JSContext* aCx, JS::Handle<JS::Value> aValue,
                              ErrorResult& aError)
{
  SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetInnerWidth,
                            aValue, "innerWidth", aError);
}

int32_t
nsGlobalWindow::GetInnerHeightOuter(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  CSSIntSize size;
  aError = GetInnerSize(size);
  return size.height;
}

int32_t
nsGlobalWindow::GetInnerHeight(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetInnerHeightOuter, (aError), aError, 0);
}

void
nsGlobalWindow::GetInnerHeight(JSContext* aCx,
                              JS::MutableHandle<JS::Value> aValue,
                              ErrorResult& aError)
{
  GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetInnerHeight, aValue,
                            aError);
}

nsresult
nsGlobalWindow::GetInnerHeight(int32_t* aInnerHeight)
{
  FORWARD_TO_INNER(GetInnerHeight, (aInnerHeight), NS_ERROR_UNEXPECTED);

  ErrorResult rv;
  *aInnerHeight = GetInnerHeight(rv);

  return rv.StealNSResult();
}

void
nsGlobalWindow::SetInnerHeightOuter(int32_t aInnerHeight, ErrorResult& aError, bool aCallerIsChrome)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  if (!mDocShell) {
    aError.Throw(NS_ERROR_UNEXPECTED);
    return;
  }

  RefPtr<nsIPresShell> presShell = mDocShell->GetPresShell();

  if (presShell && presShell->GetIsViewportOverridden())
  {
    RefPtr<nsPresContext> presContext;
    presContext = presShell->GetPresContext();

    nsRect shellArea = presContext->GetVisibleArea();
    nscoord height = aInnerHeight;
    nscoord width = shellArea.width;
    CheckSecurityWidthAndHeight(nullptr, &height, aCallerIsChrome);
    SetCSSViewportWidthAndHeight(width,
                                 nsPresContext::CSSPixelsToAppUnits(height));
    return;
  }

  int32_t height = 0;
  int32_t width  = 0;

  nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(mDocShell));
  docShellAsWin->GetSize(&width, &height);
  CheckSecurityWidthAndHeight(nullptr, &aInnerHeight, aCallerIsChrome);
  aError = SetDocShellWidthAndHeight(width, CSSToDevIntPixels(aInnerHeight));
}

void
nsGlobalWindow::SetInnerHeight(int32_t aInnerHeight, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetInnerHeightOuter, (aInnerHeight, aError, nsContentUtils::IsCallerChrome()), aError, );
}

void
nsGlobalWindow::SetInnerHeight(JSContext* aCx, JS::Handle<JS::Value> aValue,
                               ErrorResult& aError)
{
  SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetInnerHeight,
                            aValue, "innerHeight", aError);
}

nsIntSize
nsGlobalWindow::GetOuterSize(ErrorResult& aError)
{
  MOZ_ASSERT(IsOuterWindow());

  if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
    CSSIntSize size;
    aError = GetInnerSize(size);
    return nsIntSize(size.width, size.height);
  }

  nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
  if (!treeOwnerAsWin) {
    aError.Throw(NS_ERROR_FAILURE);
    return nsIntSize(0, 0);
  }

  nsGlobalWindow* rootWindow = nsGlobalWindow::Cast(GetPrivateRoot());
  if (rootWindow) {
    rootWindow->FlushPendingNotifications(Flush_Layout);
  }

  nsIntSize sizeDevPixels;
  aError = treeOwnerAsWin->GetSize(&sizeDevPixels.width, &sizeDevPixels.height);
  if (aError.Failed()) {
    return nsIntSize();
  }

  return DevToCSSIntPixels(sizeDevPixels);
}

int32_t
nsGlobalWindow::GetOuterWidthOuter(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());
  return GetOuterSize(aError).width;
}

int32_t
nsGlobalWindow::GetOuterWidth(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetOuterWidthOuter, (aError), aError, 0);
}

void
nsGlobalWindow::GetOuterWidth(JSContext* aCx,
                              JS::MutableHandle<JS::Value> aValue,
                              ErrorResult& aError)
{
  GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetOuterWidth, aValue,
                            aError);
}

int32_t
nsGlobalWindow::GetOuterHeightOuter(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());
  return GetOuterSize(aError).height;
}

int32_t
nsGlobalWindow::GetOuterHeight(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetOuterHeightOuter, (aError), aError, 0);
}

void
nsGlobalWindow::GetOuterHeight(JSContext* aCx,
                               JS::MutableHandle<JS::Value> aValue,
                               ErrorResult& aError)
{
  GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetOuterHeight, aValue,
                            aError);
}

void
nsGlobalWindow::SetOuterSize(int32_t aLengthCSSPixels, bool aIsWidth,
                             ErrorResult& aError, bool aCallerIsChrome)
{
  MOZ_ASSERT(IsOuterWindow());

  nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
  if (!treeOwnerAsWin) {
    aError.Throw(NS_ERROR_FAILURE);
    return;
  }

  CheckSecurityWidthAndHeight(aIsWidth ? &aLengthCSSPixels : nullptr,
                              aIsWidth ? nullptr : &aLengthCSSPixels,
                              aCallerIsChrome);

  int32_t width, height;
  aError = treeOwnerAsWin->GetSize(&width, &height);
  if (aError.Failed()) {
    return;
  }

  int32_t lengthDevPixels = CSSToDevIntPixels(aLengthCSSPixels);
  if (aIsWidth) {
    width = lengthDevPixels;
  } else {
    height = lengthDevPixels;
  }
  aError = treeOwnerAsWin->SetSize(width, height, true);

  CheckForDPIChange();
}

void
nsGlobalWindow::SetOuterWidthOuter(int32_t aOuterWidth, ErrorResult& aError, bool aCallerIsChrome)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  SetOuterSize(aOuterWidth, true, aError, aCallerIsChrome);
}

void
nsGlobalWindow::SetOuterWidth(int32_t aOuterWidth, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetOuterWidthOuter, (aOuterWidth, aError, nsContentUtils::IsCallerChrome()), aError, );
}

void
nsGlobalWindow::SetOuterWidth(JSContext* aCx, JS::Handle<JS::Value> aValue,
                              ErrorResult& aError)
{
  SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetOuterWidth,
                            aValue, "outerWidth", aError);
}

void
nsGlobalWindow::SetOuterHeightOuter(int32_t aOuterHeight, ErrorResult& aError, bool aCallerIsChrome)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  SetOuterSize(aOuterHeight, false, aError, aCallerIsChrome);
}

void
nsGlobalWindow::SetOuterHeight(int32_t aOuterHeight, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetOuterHeightOuter, (aOuterHeight, aError, nsContentUtils::IsCallerChrome()), aError, );
}

void
nsGlobalWindow::SetOuterHeight(JSContext* aCx, JS::Handle<JS::Value> aValue,
                               ErrorResult& aError)
{
  SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetOuterHeight,
                            aValue, "outerHeight", aError);
}

CSSIntPoint
nsGlobalWindow::GetScreenXY(ErrorResult& aError)
{
  MOZ_ASSERT(IsOuterWindow());

  // When resisting fingerprinting, always return (0,0)
  if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
    return CSSIntPoint(0, 0);
  }

  nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
  if (!treeOwnerAsWin) {
    aError.Throw(NS_ERROR_FAILURE);
    return CSSIntPoint(0, 0);
  }

  int32_t x = 0, y = 0;
  aError = treeOwnerAsWin->GetPosition(&x, &y); // LayoutDevice px values

  RefPtr<nsPresContext> presContext;
  mDocShell->GetPresContext(getter_AddRefs(presContext));
  if (!presContext) {
    return CSSIntPoint(x, y);
  }

  // Find the global desktop coordinate of the top-left of the screen.
  // We'll use this as a "fake origin" when converting to CSS px units,
  // to avoid overlapping coordinates in cases such as a hi-dpi screen
  // placed to the right of a lo-dpi screen on Windows. (Instead, there
  // may be "gaps" in the resulting CSS px coordinates in some cases.)
  nsDeviceContext *dc = presContext->DeviceContext();
  nsRect screenRect;
  dc->GetRect(screenRect);
  LayoutDeviceRect screenRectDev =
    LayoutDevicePixel::FromAppUnits(screenRect, dc->AppUnitsPerDevPixel());

  DesktopToLayoutDeviceScale scale = dc->GetDesktopToDeviceScale();
  DesktopRect screenRectDesk = screenRectDev / scale;

  CSSPoint cssPt =
    LayoutDevicePoint(x - screenRectDev.x, y - screenRectDev.y) /
    presContext->CSSToDevPixelScale();
  cssPt.x += screenRectDesk.x;
  cssPt.y += screenRectDesk.y;

  return CSSIntPoint(NSToIntRound(cssPt.x), NSToIntRound(cssPt.y));
}

int32_t
nsGlobalWindow::GetScreenXOuter(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  return GetScreenXY(aError).x;
}

int32_t
nsGlobalWindow::GetScreenX(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetScreenXOuter, (aError), aError, 0);
}

void
nsGlobalWindow::GetScreenX(JSContext* aCx,
                           JS::MutableHandle<JS::Value> aValue,
                           ErrorResult& aError)
{
  GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetScreenX, aValue,
                            aError);
}

nsRect
nsGlobalWindow::GetInnerScreenRect()
{
  MOZ_ASSERT(IsOuterWindow());

  if (!mDocShell) {
    return nsRect();
  }

  nsGlobalWindow* rootWindow = nsGlobalWindow::Cast(GetPrivateRoot());
  if (rootWindow) {
    rootWindow->FlushPendingNotifications(Flush_Layout);
  }

  if (!mDocShell) {
    return nsRect();
  }

  nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
  if (!presShell) {
    return nsRect();
  }
  nsIFrame* rootFrame = presShell->GetRootFrame();
  if (!rootFrame) {
    return nsRect();
  }

  return rootFrame->GetScreenRectInAppUnits();
}

float
nsGlobalWindow::GetMozInnerScreenXOuter()
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  // When resisting fingerprinting, always return 0.
  if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
    return 0.0;
  }

  nsRect r = GetInnerScreenRect();
  return nsPresContext::AppUnitsToFloatCSSPixels(r.x);
}

float
nsGlobalWindow::GetMozInnerScreenX(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenXOuter, (), aError, 0);
}

float
nsGlobalWindow::GetMozInnerScreenYOuter()
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  // Return 0 to prevent fingerprinting.
  if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
    return 0.0;
  }

  nsRect r = GetInnerScreenRect();
  return nsPresContext::AppUnitsToFloatCSSPixels(r.y);
}

float
nsGlobalWindow::GetMozInnerScreenY(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenYOuter, (), aError, 0);
}

float
nsGlobalWindow::GetDevicePixelRatioOuter()
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  if (!mDocShell) {
    return 1.0;
  }

  RefPtr<nsPresContext> presContext;
  mDocShell->GetPresContext(getter_AddRefs(presContext));
  if (!presContext) {
    return 1.0;
  }

  if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
    return 1.0;
  }

  float overrideDPPX = presContext->GetOverrideDPPX();

  if (overrideDPPX > 0) {
    return overrideDPPX;
  }

  return float(nsPresContext::AppUnitsPerCSSPixel())/
      presContext->AppUnitsPerDevPixel();
}

float
nsGlobalWindow::GetDevicePixelRatio(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetDevicePixelRatioOuter, (), aError, 0.0);
}

nsresult
nsGlobalWindow::GetDevicePixelRatio(float* aRatio)
{
  FORWARD_TO_INNER(GetDevicePixelRatio, (aRatio), NS_ERROR_UNEXPECTED);

  ErrorResult rv;
  *aRatio = GetDevicePixelRatio(rv);

  return rv.StealNSResult();
}

uint64_t
nsGlobalWindow::GetMozPaintCountOuter()
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  if (!mDocShell) {
    return 0;
  }

  nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
  return presShell ? presShell->GetPaintCount() : 0;
}

uint64_t
nsGlobalWindow::GetMozPaintCount(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetMozPaintCountOuter, (), aError, 0);
}

int32_t
nsGlobalWindow::RequestAnimationFrame(FrameRequestCallback& aCallback,
                                      ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());

  if (!mDoc) {
    return 0;
  }

  if (GetWrapperPreserveColor()) {
    js::NotifyAnimationActivity(GetWrapperPreserveColor());
  }

  int32_t handle;
  aError = mDoc->ScheduleFrameRequestCallback(aCallback, &handle);
  return handle;
}

void
nsGlobalWindow::CancelAnimationFrame(int32_t aHandle, ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsInnerWindow());

  if (!mDoc) {
    return;
  }

  mDoc->CancelFrameRequestCallback(aHandle);
}

already_AddRefed<MediaQueryList>
nsGlobalWindow::MatchMediaOuter(const nsAString& aMediaQueryList)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  if (!mDoc) {
    return nullptr;
  }

  return mDoc->MatchMedia(aMediaQueryList);
}

already_AddRefed<MediaQueryList>
nsGlobalWindow::MatchMedia(const nsAString& aMediaQueryList,
                           ErrorResult& aError)
{
  // FIXME: This whole forward-to-outer and then get a pres
  // shell/context off the docshell dance is sort of silly; it'd make
  // more sense to forward to the inner, but it's what everyone else
  // (GetSelection, GetScrollXY, etc.) does around here.
  FORWARD_TO_OUTER_OR_THROW(MatchMediaOuter, (aMediaQueryList), aError, nullptr);
}

void
nsGlobalWindow::SetScreenXOuter(int32_t aScreenX, ErrorResult& aError, bool aCallerIsChrome)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
  if (!treeOwnerAsWin) {
    aError.Throw(NS_ERROR_FAILURE);
    return;
  }

  int32_t x, y;
  aError = treeOwnerAsWin->GetPosition(&x, &y);
  if (aError.Failed()) {
    return;
  }

  CheckSecurityLeftAndTop(&aScreenX, nullptr, aCallerIsChrome);
  x = CSSToDevIntPixels(aScreenX);

  aError = treeOwnerAsWin->SetPosition(x, y);

  CheckForDPIChange();
}

void
nsGlobalWindow::SetScreenX(int32_t aScreenX, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetScreenXOuter, (aScreenX, aError, nsContentUtils::IsCallerChrome()), aError, );
}

void
nsGlobalWindow::SetScreenX(JSContext* aCx, JS::Handle<JS::Value> aValue,
                           ErrorResult& aError)
{
  SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetScreenX,
                            aValue, "screenX", aError);
}

int32_t
nsGlobalWindow::GetScreenYOuter(ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  return GetScreenXY(aError).y;
}

int32_t
nsGlobalWindow::GetScreenY(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetScreenYOuter, (aError), aError, 0);
}

void
nsGlobalWindow::GetScreenY(JSContext* aCx,
                           JS::MutableHandle<JS::Value> aValue,
                           ErrorResult& aError)
{
  GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetScreenY, aValue,
                            aError);
}

void
nsGlobalWindow::SetScreenYOuter(int32_t aScreenY, ErrorResult& aError, bool aCallerIsChrome)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
  if (!treeOwnerAsWin) {
    aError.Throw(NS_ERROR_FAILURE);
    return;
  }

  int32_t x, y;
  aError = treeOwnerAsWin->GetPosition(&x, &y);
  if (aError.Failed()) {
    return;
  }

  CheckSecurityLeftAndTop(nullptr, &aScreenY, aCallerIsChrome);
  y = CSSToDevIntPixels(aScreenY);

  aError = treeOwnerAsWin->SetPosition(x, y);

  CheckForDPIChange();
}

void
nsGlobalWindow::SetScreenY(int32_t aScreenY, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetScreenYOuter, (aScreenY, aError, nsContentUtils::IsCallerChrome()), aError, );
}

void
nsGlobalWindow::SetScreenY(JSContext* aCx, JS::Handle<JS::Value> aValue,
                           ErrorResult& aError)
{
  SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetScreenY,
                            aValue, "screenY", aError);
}

// NOTE: Arguments to this function should have values scaled to
// CSS pixels, not device pixels.
void
nsGlobalWindow::CheckSecurityWidthAndHeight(int32_t* aWidth, int32_t* aHeight, bool aCallerIsChrome)
{
  MOZ_ASSERT(IsOuterWindow());

#ifdef MOZ_XUL
  if (!aCallerIsChrome) {
    // if attempting to resize the window, hide any open popups
    nsContentUtils::HidePopupsInDocument(mDoc);
  }
#endif

  // This one is easy. Just ensure the variable is greater than 100;
  if ((aWidth && *aWidth < 100) || (aHeight && *aHeight < 100)) {
    // Check security state for use in determing window dimensions

    if (!nsContentUtils::IsCallerChrome()) {
      //sec check failed
      if (aWidth && *aWidth < 100) {
        *aWidth = 100;
      }
      if (aHeight && *aHeight < 100) {
        *aHeight = 100;
      }
    }
  }
}

// NOTE: Arguments to this function should have values in device pixels
nsresult
nsGlobalWindow::SetDocShellWidthAndHeight(int32_t aInnerWidth, int32_t aInnerHeight)
{
  MOZ_ASSERT(IsOuterWindow());

  NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);

  nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
  mDocShell->GetTreeOwner(getter_AddRefs(treeOwner));
  NS_ENSURE_TRUE(treeOwner, NS_ERROR_FAILURE);

  NS_ENSURE_SUCCESS(treeOwner->SizeShellTo(mDocShell, aInnerWidth, aInnerHeight),
                    NS_ERROR_FAILURE);

  return NS_OK;
}

// NOTE: Arguments to this function should have values in app units
void
nsGlobalWindow::SetCSSViewportWidthAndHeight(nscoord aInnerWidth, nscoord aInnerHeight)
{
  MOZ_ASSERT(IsOuterWindow());

  RefPtr<nsPresContext> presContext;
  mDocShell->GetPresContext(getter_AddRefs(presContext));

  nsRect shellArea = presContext->GetVisibleArea();
  shellArea.height = aInnerHeight;
  shellArea.width = aInnerWidth;

  presContext->SetVisibleArea(shellArea);
}

// NOTE: Arguments to this function should have values scaled to
// CSS pixels, not device pixels.
void
nsGlobalWindow::CheckSecurityLeftAndTop(int32_t* aLeft, int32_t* aTop, bool aCallerIsChrome)
{
  MOZ_ASSERT(IsOuterWindow());

  // This one is harder. We have to get the screen size and window dimensions.

  // Check security state for use in determing window dimensions

  if (!aCallerIsChrome) {
#ifdef MOZ_XUL
    // if attempting to move the window, hide any open popups
    nsContentUtils::HidePopupsInDocument(mDoc);
#endif

    if (nsGlobalWindow* rootWindow = nsGlobalWindow::Cast(GetPrivateRoot())) {
      rootWindow->FlushPendingNotifications(Flush_Layout);
    }

    nsCOMPtr<nsIBaseWindow> treeOwner = GetTreeOwnerWindow();

    nsCOMPtr<nsIDOMScreen> screen = GetScreen();

    if (treeOwner && screen) {
      int32_t screenLeft, screenTop, screenWidth, screenHeight;
      int32_t winLeft, winTop, winWidth, winHeight;

      // Get the window size
      treeOwner->GetPositionAndSize(&winLeft, &winTop, &winWidth, &winHeight);

      // convert those values to CSS pixels
      // XXX four separate retrievals of the prescontext
      winLeft   = DevToCSSIntPixels(winLeft);
      winTop    = DevToCSSIntPixels(winTop);
      winWidth  = DevToCSSIntPixels(winWidth);
      winHeight = DevToCSSIntPixels(winHeight);

      // Get the screen dimensions
      // XXX This should use nsIScreenManager once it's fully fleshed out.
      screen->GetAvailLeft(&screenLeft);
      screen->GetAvailWidth(&screenWidth);
      screen->GetAvailHeight(&screenHeight);
#if defined(XP_MACOSX)
      /* The mac's coordinate system is different from the assumed Windows'
         system. It offsets by the height of the menubar so that a window
         placed at (0,0) will be entirely visible. Unfortunately that
         correction is made elsewhere (in Widget) and the meaning of
         the Avail... coordinates is overloaded. Here we allow a window
         to be placed at (0,0) because it does make sense to do so.
      */
      screen->GetTop(&screenTop);
#else
      screen->GetAvailTop(&screenTop);
#endif

      if (aLeft) {
        if (screenLeft+screenWidth < *aLeft+winWidth)
          *aLeft = screenLeft+screenWidth - winWidth;
        if (screenLeft > *aLeft)
          *aLeft = screenLeft;
      }
      if (aTop) {
        if (screenTop+screenHeight < *aTop+winHeight)
          *aTop = screenTop+screenHeight - winHeight;
        if (screenTop > *aTop)
          *aTop = screenTop;
      }
    } else {
      if (aLeft)
        *aLeft = 0;
      if (aTop)
        *aTop = 0;
    }
  }
}

int32_t
nsGlobalWindow::GetScrollBoundaryOuter(Side aSide)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  FlushPendingNotifications(Flush_Layout);
  if (nsIScrollableFrame *sf = GetScrollFrame()) {
    return nsPresContext::
      AppUnitsToIntCSSPixels(sf->GetScrollRange().Edge(aSide));
  }
  return 0;
}

int32_t
nsGlobalWindow::GetScrollMinX(ErrorResult& aError)
{
  MOZ_ASSERT(IsInnerWindow());
  FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideLeft), aError, 0);
}

int32_t
nsGlobalWindow::GetScrollMinY(ErrorResult& aError)
{
  MOZ_ASSERT(IsInnerWindow());
  FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideTop), aError, 0);
}

int32_t
nsGlobalWindow::GetScrollMaxX(ErrorResult& aError)
{
  MOZ_ASSERT(IsInnerWindow());
  FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideRight), aError, 0);
}

int32_t
nsGlobalWindow::GetScrollMaxY(ErrorResult& aError)
{
  MOZ_ASSERT(IsInnerWindow());
  FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideBottom), aError, 0);
}

CSSIntPoint
nsGlobalWindow::GetScrollXY(bool aDoFlush)
{
  MOZ_ASSERT(IsOuterWindow());

  if (aDoFlush) {
    FlushPendingNotifications(Flush_Layout);
  } else {
    EnsureSizeUpToDate();
  }

  nsIScrollableFrame *sf = GetScrollFrame();
  if (!sf) {
    return CSSIntPoint(0, 0);
  }

  nsPoint scrollPos = sf->GetScrollPosition();
  if (scrollPos != nsPoint(0,0) && !aDoFlush) {
    // Oh, well.  This is the expensive case -- the window is scrolled and we
    // didn't actually flush yet.  Repeat, but with a flush, since the content
    // may get shorter and hence our scroll position may decrease.
    return GetScrollXY(true);
  }

  return sf->GetScrollPositionCSSPixels();
}

int32_t
nsGlobalWindow::GetScrollXOuter()
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());
  return GetScrollXY(false).x;
}

int32_t
nsGlobalWindow::GetScrollX(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetScrollXOuter, (), aError, 0);
}

int32_t
nsGlobalWindow::GetScrollYOuter()
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());
  return GetScrollXY(false).y;
}

int32_t
nsGlobalWindow::GetScrollY(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetScrollYOuter, (), aError, 0);
}

uint32_t
nsGlobalWindow::Length()
{
  FORWARD_TO_OUTER(Length, (), 0);

  nsDOMWindowList* windows = GetWindowList();

  return windows ? windows->GetLength() : 0;
}

already_AddRefed<nsPIDOMWindowOuter>
nsGlobalWindow::GetTopOuter()
{
  MOZ_ASSERT(IsOuterWindow());

  nsCOMPtr<nsPIDOMWindowOuter> top = GetScriptableTop();
  return top.forget();
}

already_AddRefed<nsPIDOMWindowOuter>
nsGlobalWindow::GetTop(mozilla::ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetTopOuter, (), aError, nullptr);
}

nsPIDOMWindowOuter*
nsGlobalWindow::GetChildWindow(const nsAString& aName)
{
  nsCOMPtr<nsIDocShell> docShell(GetDocShell());
  NS_ENSURE_TRUE(docShell, nullptr);

  nsCOMPtr<nsIDocShellTreeItem> child;
  docShell->FindChildWithName(aName, false, true, nullptr, nullptr,
                              getter_AddRefs(child));

  return child ? child->GetWindow() : nullptr;
}

bool
nsGlobalWindow::DispatchCustomEvent(const nsAString& aEventName)
{
  MOZ_ASSERT(IsOuterWindow());

  bool defaultActionEnabled = true;
  nsContentUtils::DispatchTrustedEvent(mDoc, ToSupports(this), aEventName,
                                       true, true, &defaultActionEnabled);

  return defaultActionEnabled;
}

bool
nsGlobalWindow::DispatchResizeEvent(const CSSIntSize& aSize)
{
  MOZ_ASSERT(IsOuterWindow());

  ErrorResult res;
  RefPtr<Event> domEvent =
    mDoc->CreateEvent(NS_LITERAL_STRING("CustomEvent"), res);
  if (res.Failed()) {
    return false;
  }

  // We don't init the AutoJSAPI with ourselves because we don't want it
  // reporting errors to our onerror handlers.
  AutoJSAPI jsapi;
  jsapi.Init();
  JSContext* cx = jsapi.cx();
  JSAutoCompartment ac(cx, GetWrapperPreserveColor());

  DOMWindowResizeEventDetail detail;
  detail.mWidth = aSize.width;
  detail.mHeight = aSize.height;
  JS::Rooted<JS::Value> detailValue(cx);
  if (!ToJSValue(cx, detail, &detailValue)) {
    return false;
  }

  CustomEvent* customEvent = static_cast<CustomEvent*>(domEvent.get());
  customEvent->InitCustomEvent(cx,
                               NS_LITERAL_STRING("DOMWindowResize"),
                               /* aCanBubble = */ true,
                               /* aCancelable = */ true,
                               detailValue,
                               res);
  if (res.Failed()) {
    return false;
  }

  domEvent->SetTrusted(true);
  domEvent->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;

  nsCOMPtr<EventTarget> target = do_QueryInterface(GetOuterWindow());
  domEvent->SetTarget(target);

  bool defaultActionEnabled = true;
  target->DispatchEvent(domEvent, &defaultActionEnabled);

  return defaultActionEnabled;
}

void
nsGlobalWindow::RefreshCompartmentPrincipal()
{
  MOZ_ASSERT(IsInnerWindow());

  JS_SetCompartmentPrincipals(js::GetObjectCompartment(GetWrapperPreserveColor()),
                              nsJSPrincipals::get(mDoc->NodePrincipal()));
}

static already_AddRefed<nsIDocShellTreeItem>
GetCallerDocShellTreeItem()
{
  nsCOMPtr<nsIWebNavigation> callerWebNav = do_GetInterface(GetEntryGlobal());
  nsCOMPtr<nsIDocShellTreeItem> callerItem = do_QueryInterface(callerWebNav);

  return callerItem.forget();
}

bool
nsGlobalWindow::WindowExists(const nsAString& aName,
                             bool aForceNoOpener,
                             bool aLookForCallerOnJSStack)
{
  NS_PRECONDITION(IsOuterWindow(), "Must be outer window");
  NS_PRECONDITION(mDocShell, "Must have docshell");

  if (aForceNoOpener) {
    return aName.LowerCaseEqualsLiteral("_self") ||
           aName.LowerCaseEqualsLiteral("_top") ||
           aName.LowerCaseEqualsLiteral("_parent");
  }

  nsCOMPtr<nsIDocShellTreeItem> caller;
  if (aLookForCallerOnJSStack) {
    caller = GetCallerDocShellTreeItem();
  }

  if (!caller) {
    caller = mDocShell;
  }

  nsCOMPtr<nsIDocShellTreeItem> namedItem;
  mDocShell->FindItemWithName(aName, nullptr, caller,
                              getter_AddRefs(namedItem));
  return namedItem != nullptr;
}

already_AddRefed<nsIWidget>
nsGlobalWindow::GetMainWidget()
{
  FORWARD_TO_OUTER(GetMainWidget, (), nullptr);

  nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();

  nsCOMPtr<nsIWidget> widget;

  if (treeOwnerAsWin) {
    treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget));
  }

  return widget.forget();
}

nsIWidget*
nsGlobalWindow::GetNearestWidget() const
{
  nsIDocShell* docShell = GetDocShell();
  NS_ENSURE_TRUE(docShell, nullptr);
  nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
  NS_ENSURE_TRUE(presShell, nullptr);
  nsIFrame* rootFrame = presShell->GetRootFrame();
  NS_ENSURE_TRUE(rootFrame, nullptr);
  return rootFrame->GetView()->GetNearestWidget(nullptr);
}

void
nsGlobalWindow::SetFullScreenOuter(bool aFullScreen, mozilla::ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsOuterWindow());

  aError = SetFullscreenInternal(FullscreenReason::ForFullscreenMode, aFullScreen);
}

void
nsGlobalWindow::SetFullScreen(bool aFullScreen, mozilla::ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetFullScreenOuter, (aFullScreen, aError), aError, /* void */);
}

nsresult
nsGlobalWindow::SetFullScreen(bool aFullScreen)
{
  FORWARD_TO_OUTER(SetFullScreen, (aFullScreen), NS_ERROR_NOT_INITIALIZED);

  return SetFullscreenInternal(FullscreenReason::ForFullscreenMode, aFullScreen);
}

static void
FinishDOMFullscreenChange(nsIDocument* aDoc, bool aInDOMFullscreen)
{
  if (aInDOMFullscreen) {
    // Ask the document to handle any pending DOM fullscreen change.
    if (!nsIDocument::HandlePendingFullscreenRequests(aDoc)) {
      // If we don't end up having anything in fullscreen,
      // async request exiting fullscreen.
      nsIDocument::AsyncExitFullscreen(aDoc);
    }
  } else {
    // If the window is leaving fullscreen state, also ask the document
    // to exit from DOM Fullscreen.
    nsIDocument::ExitFullscreenInDocTree(aDoc);
  }
}

struct FullscreenTransitionDuration
{
  // The unit of the durations is millisecond
  uint16_t mFadeIn = 0;
  uint16_t mFadeOut = 0;
  bool IsSuppressed() const
  {
    return mFadeIn == 0 && mFadeOut == 0;
  }
};

static void
GetFullscreenTransitionDuration(bool aEnterFullscreen,
                                FullscreenTransitionDuration* aDuration)
{
  const char* pref = aEnterFullscreen ?
    "full-screen-api.transition-duration.enter" :
    "full-screen-api.transition-duration.leave";
  nsAdoptingCString prefValue = Preferences::GetCString(pref);
  if (!prefValue.IsEmpty()) {
    sscanf(prefValue.get(), "%hu%hu",
           &aDuration->mFadeIn, &aDuration->mFadeOut);
  }
}

class FullscreenTransitionTask : public Runnable
{
public:
  FullscreenTransitionTask(const FullscreenTransitionDuration& aDuration,
                           nsGlobalWindow* aWindow, bool aFullscreen,
                           nsIWidget* aWidget, nsIScreen* aScreen,
                           nsISupports* aTransitionData)
    : mWindow(aWindow)
    , mWidget(aWidget)
    , mScreen(aScreen)
    , mTransitionData(aTransitionData)
    , mDuration(aDuration)
    , mStage(eBeforeToggle)
    , mFullscreen(aFullscreen)
  {
    MOZ_COUNT_CTOR(FullscreenTransitionTask);
  }

  NS_IMETHOD Run() override;

private:
  ~FullscreenTransitionTask() override
  {
    MOZ_COUNT_DTOR(FullscreenTransitionTask);
  }

  /**
   * The flow of fullscreen transition:
   *
   *         parent process         |         child process
   * ----------------------------------------------------------------
   *
   *                                    | request/exit fullscreen
   *                              <-----|
   *         BeforeToggle stage |
   *                            |
   *  ToggleFullscreen stage *1 |----->
   *                                    | HandleFullscreenRequests
   *                                    |
   *                              <-----| MozAfterPaint event
   *       AfterToggle stage *2 |
   *                            |
   *                  End stage |
   *
   * Note we also start a timer at *1 so that if we don't get MozAfterPaint
   * from the child process in time, we continue going to *2.
   */
  enum Stage {
    // BeforeToggle stage happens before we enter or leave fullscreen
    // state. In this stage, the task triggers the pre-toggle fullscreen
    // transition on the widget.
    eBeforeToggle,
    // ToggleFullscreen stage actually executes the fullscreen toggle,
    // and wait for the next paint on the content to continue.
    eToggleFullscreen,
    // AfterToggle stage happens after we toggle the fullscreen state.
    // In this stage, the task triggers the post-toggle fullscreen
    // transition on the widget.
    eAfterToggle,
    // End stage is triggered after the final transition finishes.
    eEnd
  };

  class Observer final : public nsIObserver
  {
  public:
    NS_DECL_ISUPPORTS
    NS_DECL_NSIOBSERVER

    explicit Observer(FullscreenTransitionTask* aTask)
      : mTask(aTask) { }

  private:
    ~Observer() = default;

    RefPtr<FullscreenTransitionTask> mTask;
  };

  static const char* const kPaintedTopic;

  RefPtr<nsGlobalWindow> mWindow;
  nsCOMPtr<nsIWidget> mWidget;
  nsCOMPtr<nsIScreen> mScreen;
  nsCOMPtr<nsITimer> mTimer;
  nsCOMPtr<nsISupports> mTransitionData;

  TimeStamp mFullscreenChangeStartTime;
  FullscreenTransitionDuration mDuration;
  Stage mStage;
  bool mFullscreen;
};

const char* const
FullscreenTransitionTask::kPaintedTopic = "fullscreen-painted";

NS_IMETHODIMP
FullscreenTransitionTask::Run()
{
  Stage stage = mStage;
  mStage = Stage(mStage + 1);
  if (MOZ_UNLIKELY(mWidget->Destroyed())) {
    // If the widget has been destroyed before we get here, don't try to
    // do anything more. Just let it go and release ourselves.
    NS_WARNING("The widget to fullscreen has been destroyed");
    return NS_OK;
  }
  if (stage == eBeforeToggle) {
    PROFILER_MARKER("Fullscreen transition start");
    mWidget->PerformFullscreenTransition(nsIWidget::eBeforeFullscreenToggle,
                                         mDuration.mFadeIn, mTransitionData,
                                         this);
  } else if (stage == eToggleFullscreen) {
    PROFILER_MARKER("Fullscreen toggle start");
    mFullscreenChangeStartTime = TimeStamp::Now();
    if (MOZ_UNLIKELY(mWindow->mFullScreen != mFullscreen)) {
      // This could happen in theory if several fullscreen requests in
      // different direction happen continuously in a short time. We
      // need to ensure the fullscreen state matches our target here,
      // otherwise the widget would change the window state as if we
      // toggle for Fullscreen Mode instead of Fullscreen API.
      NS_WARNING("The fullscreen state of the window does not match");
      mWindow->mFullScreen = mFullscreen;
    }
    // Toggle the fullscreen state on the widget
    if (!mWindow->SetWidgetFullscreen(FullscreenReason::ForFullscreenAPI,
                                      mFullscreen, mWidget, mScreen)) {
      // Fail to setup the widget, call FinishFullscreenChange to
      // complete fullscreen change directly.
      mWindow->FinishFullscreenChange(mFullscreen);
    }
    // Set observer for the next content paint.
    nsCOMPtr<nsIObserver> observer = new Observer(this);
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    obs->AddObserver(observer, kPaintedTopic, false);
    // There are several edge cases where we may never get the paint
    // notification, including:
    // 1. the window/tab is closed before the next paint;
    // 2. the user has switched to another tab before we get here.
    // Completely fixing those cases seems to be tricky, and since they
    // should rarely happen, it probably isn't worth to fix. Hence we
    // simply add a timeout here to ensure we never hang forever.
    // In addition, if the page is complicated or the machine is less
    // powerful, layout could take a long time, in which case, staying
    // in black screen for that long could hurt user experience even
    // more than exposing an intermediate state.
    mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
    uint32_t timeout =
      Preferences::GetUint("full-screen-api.transition.timeout", 1000);
    mTimer->Init(observer, timeout, nsITimer::TYPE_ONE_SHOT);
  } else if (stage == eAfterToggle) {
    Telemetry::AccumulateTimeDelta(Telemetry::FULLSCREEN_TRANSITION_BLACK_MS,
                                   mFullscreenChangeStartTime);
    mWidget->PerformFullscreenTransition(nsIWidget::eAfterFullscreenToggle,
                                         mDuration.mFadeOut, mTransitionData,
                                         this);
  } else if (stage == eEnd) {
    PROFILER_MARKER("Fullscreen transition end");
  }
  return NS_OK;
}

NS_IMPL_ISUPPORTS(FullscreenTransitionTask::Observer, nsIObserver)

NS_IMETHODIMP
FullscreenTransitionTask::Observer::Observe(nsISupports* aSubject,
                                            const char* aTopic,
                                            const char16_t* aData)
{
  bool shouldContinue = false;
  if (strcmp(aTopic, FullscreenTransitionTask::kPaintedTopic) == 0) {
    nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(aSubject));
    nsCOMPtr<nsIWidget> widget = win ?
      nsGlobalWindow::Cast(win)->GetMainWidget() : nullptr;
    if (widget == mTask->mWidget) {
      // The paint notification arrives first. Cancel the timer.
      mTask->mTimer->Cancel();
      shouldContinue = true;
      PROFILER_MARKER("Fullscreen toggle end");
    }
  } else {
#ifdef DEBUG
    MOZ_ASSERT(strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0,
               "Should only get fullscreen-painted or timer-callback");
    nsCOMPtr<nsITimer> timer(do_QueryInterface(aSubject));
    MOZ_ASSERT(timer && timer == mTask->mTimer,
               "Should only trigger this with the timer the task created");
#endif
    shouldContinue = true;
    PROFILER_MARKER("Fullscreen toggle timeout");
  }
  if (shouldContinue) {
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    obs->RemoveObserver(this, kPaintedTopic);
    mTask->mTimer = nullptr;
    mTask->Run();
  }
  return NS_OK;
}

static bool
MakeWidgetFullscreen(nsGlobalWindow* aWindow, FullscreenReason aReason,
                     bool aFullscreen)
{
  nsCOMPtr<nsIWidget> widget = aWindow->GetMainWidget();
  if (!widget) {
    return false;
  }

  FullscreenTransitionDuration duration;
  bool performTransition = false;
  nsCOMPtr<nsISupports> transitionData;
  if (aReason == FullscreenReason::ForFullscreenAPI) {
    GetFullscreenTransitionDuration(aFullscreen, &duration);
    if (!duration.IsSuppressed()) {
      performTransition = widget->
        PrepareForFullscreenTransition(getter_AddRefs(transitionData));
    }
  }
  // We pass nullptr as the screen to SetWidgetFullscreen
  // and FullscreenTransitionTask, as we do not wish to override
  // the default screen selection behavior.  The screen containing
  // most of the widget will be selected.
  if (!performTransition) {
    return aWindow->SetWidgetFullscreen(aReason, aFullscreen, widget, nullptr);
  } else {
    nsCOMPtr<nsIRunnable> task =
      new FullscreenTransitionTask(duration, aWindow, aFullscreen,
                                   widget, nullptr, transitionData);
    task->Run();
    return true;
  }
}

nsresult
nsGlobalWindow::SetFullscreenInternal(FullscreenReason aReason,
                                      bool aFullScreen)
{
  MOZ_ASSERT(IsOuterWindow());
  MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(),
             "Requires safe to run script as it "
             "may call FinishDOMFullscreenChange");

  NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);

  MOZ_ASSERT(aReason != FullscreenReason::ForForceExitFullscreen || !aFullScreen,
             "FullscreenReason::ForForceExitFullscreen can "
             "only be used with exiting fullscreen");

  // Only chrome can change our fullscreen mode. Otherwise, the state
  // can only be changed for DOM fullscreen.
  if (aReason == FullscreenReason::ForFullscreenMode &&
      !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
    return NS_OK;
  }

  // SetFullScreen needs to be called on the root window, so get that
  // via the DocShell tree, and if we are not already the root,
  // call SetFullScreen on that window instead.
  nsCOMPtr<nsIDocShellTreeItem> rootItem;
  mDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
  nsCOMPtr<nsPIDOMWindowOuter> window = rootItem ? rootItem->GetWindow() : nullptr;
  if (!window)
    return NS_ERROR_FAILURE;
  if (rootItem != mDocShell)
    return window->SetFullscreenInternal(aReason, aFullScreen);

  // make sure we don't try to set full screen on a non-chrome window,
  // which might happen in embedding world
  if (mDocShell->ItemType() != nsIDocShellTreeItem::typeChrome)
    return NS_ERROR_FAILURE;

  // If we are already in full screen mode, just return.
  if (mFullScreen == aFullScreen)
    return NS_OK;

  // Note that although entering DOM fullscreen could also cause
  // consequential calls to this method, those calls will be skipped
  // at the condition above.
  if (aReason == FullscreenReason::ForFullscreenMode) {
    if (!aFullScreen && !mFullscreenMode) {
      // If we are exiting fullscreen mode, but we actually didn't
      // entered fullscreen mode, the fullscreen state was only for
      // the Fullscreen API. Change the reason here so that we can
      // perform transition for it.
      aReason = FullscreenReason::ForFullscreenAPI;
    } else {
      mFullscreenMode = aFullScreen;
    }
  } else {
    // If we are exiting from DOM fullscreen while we initially make
    // the window fullscreen because of fullscreen mode, don't restore
    // the window. But we still need to exit the DOM fullscreen state.
    if (!aFullScreen && mFullscreenMode) {
      FinishDOMFullscreenChange(mDoc, false);
      return NS_OK;
    }
  }

  // Prevent chrome documents which are still loading from resizing
  // the window after we set fullscreen mode.
  nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
  nsCOMPtr<nsIXULWindow> xulWin(do_GetInterface(treeOwnerAsWin));
  if (aFullScreen && xulWin) {
    xulWin->SetIntrinsicallySized(false);
  }

  // Set this before so if widget sends an event indicating its
  // gone full screen, the state trap above works.
  mFullScreen = aFullScreen;

  // Sometimes we don't want the top-level widget to actually go fullscreen,
  // for example in the B2G desktop client, we don't want the emulated screen
  // dimensions to appear to increase when entering fullscreen mode; we just
  // want the content to fill the entire client area of the emulator window.
  if (!Preferences::GetBool("full-screen-api.ignore-widgets", false)) {
    if (MakeWidgetFullscreen(this, aReason, aFullScreen)) {
      // The rest of code for switching fullscreen is in nsGlobalWindow::
      // FinishFullscreenChange() which will be called after sizemodechange
      // event is dispatched.
      return NS_OK;
    }
  }

  FinishFullscreenChange(aFullScreen);
  return NS_OK;
}

bool
nsGlobalWindow::SetWidgetFullscreen(FullscreenReason aReason, bool aIsFullscreen,
                                    nsIWidget* aWidget, nsIScreen* aScreen)
{
  MOZ_ASSERT(IsOuterWindow());
  MOZ_ASSERT(this == GetTopInternal(), "Only topmost window should call this");
  MOZ_ASSERT(!AsOuter()->GetFrameElementInternal(), "Content window should not call this");
  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);

  if (!NS_WARN_IF(!IsChromeWindow())) {
    auto chromeWin = static_cast<nsGlobalChromeWindow*>(this);
    if (!NS_WARN_IF(chromeWin->mFullscreenPresShell)) {
      if (nsIPresShell* shell = mDocShell->GetPresShell()) {
        if (nsRefreshDriver* rd = shell->GetRefreshDriver()) {
          chromeWin->mFullscreenPresShell = do_GetWeakReference(shell);
          MOZ_ASSERT(chromeWin->mFullscreenPresShell);
          rd->SetIsResizeSuppressed();
          rd->Freeze();
        }
      }
    }
  }
  nsresult rv = aReason == FullscreenReason::ForFullscreenMode ?
    // If we enter fullscreen for fullscreen mode, we want
    // the native system behavior.
    aWidget->MakeFullScreenWithNativeTransition(aIsFullscreen, aScreen) :
    aWidget->MakeFullScreen(aIsFullscreen, aScreen);
  return NS_SUCCEEDED(rv);
}

/* virtual */ void
nsGlobalWindow::FinishFullscreenChange(bool aIsFullscreen)
{
  MOZ_ASSERT(IsOuterWindow());

  if (aIsFullscreen != mFullScreen) {
    NS_WARNING("Failed to toggle fullscreen state of the widget");
    // We failed to make the widget enter fullscreen.
    // Stop further changes and restore the state.
    if (!aIsFullscreen) {
      mFullScreen = false;
      mFullscreenMode = false;
    } else {
      MOZ_ASSERT_UNREACHABLE("Failed to exit fullscreen?");
      mFullScreen = true;
      // We don't know how code can reach here. Not sure