/* -*- 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/DOMJSProxyHandler.h"
#include "mozilla/dom/DOMPrefs.h"
#include "mozilla/dom/EventTarget.h"
#include "mozilla/dom/LocalStorage.h"
#include "mozilla/dom/Storage.h"
#include "mozilla/dom/IdleRequest.h"
#include "mozilla/dom/Performance.h"
#include "mozilla/dom/StorageEvent.h"
#include "mozilla/dom/StorageEventBinding.h"
#include "mozilla/dom/StorageNotifierService.h"
#include "mozilla/dom/StorageUtils.h"
#include "mozilla/dom/Timeout.h"
#include "mozilla/dom/TimeoutHandler.h"
#include "mozilla/dom/TimeoutManager.h"
#include "mozilla/IntegerPrintfMacros.h"
#if defined(MOZ_WIDGET_ANDROID)
#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 "nsDOMWindowList.h"
#include "mozilla/dom/WakeLock.h"
#include "mozilla/dom/power/PowerManagerService.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIDocumentLoader.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIPermissionManager.h"
#include "nsIScriptContext.h"
#include "nsIScriptTimeoutHandler.h"
#include "nsITimeoutHandler.h"
#include "nsIController.h"
#include "nsISlowScriptDebug.h"
#include "nsWindowMemoryReporter.h"
#include "nsWindowSizes.h"
#include "WindowNamedPropertiesHandler.h"
#include "nsFrameSelection.h"
#include "nsNetUtil.h"
#include "nsVariant.h"
#include "nsPrintfCString.h"
#include "mozilla/intl/LocaleService.h"
#include "WindowDestroyedEvent.h"

// Helper Classes
#include "nsJSUtils.h"
#include "jsapi.h"              // for JSAutoRequest
#include "js/Wrapper.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsReadableUtils.h"
#include "nsJSEnvironment.h"
#include "mozilla/dom/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/WorkerCommon.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 "nsIDocument.h"
#include "Crypto.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 "nsIPrompt.h"
#include "nsIPromptService.h"
#include "nsIPromptFactory.h"
#include "nsIAddonPolicyService.h"
#include "nsIWritablePropertyBag2.h"
#include "nsIWebNavigation.h"
#include "nsIWebBrowserChrome.h"
#include "nsIWebBrowserFind.h"  // For window.find()
#include "nsIWindowMediator.h"  // For window.find()
#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 "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 "nsICSSDeclaration.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"

#include "nsBindingManager.h"
#include "nsXBLService.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 "mozilla/dom/IDBFactory.h"
#include "mozilla/dom/MessageChannel.h"
#include "mozilla/dom/Promise.h"

#include "mozilla/dom/Gamepad.h"
#include "mozilla/dom/GamepadManager.h"

#include "gfxVR.h"
#include "mozilla/dom/VRDisplay.h"
#include "mozilla/dom/VRDisplayEvent.h"
#include "mozilla/dom/VRDisplayEventBinding.h"
#include "mozilla/dom/VREventObserver.h"

#include "nsRefreshDriver.h"
#include "Layers.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 "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/IntlUtils.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/InstallTriggerBinding.h"
#include "mozilla/dom/ServiceWorker.h"
#include "mozilla/dom/ServiceWorkerRegistration.h"
#include "mozilla/dom/ServiceWorkerRegistrationDescriptor.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

#include "mozilla/dom/ClientManager.h"
#include "mozilla/dom/ClientSource.h"
#include "mozilla/dom/ClientState.h"

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

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

#define FORWARD_TO_OUTER(method, args, err_rval)                        \
  PR_BEGIN_MACRO                                                        \
  nsGlobalWindowOuter *outer = GetOuterWindowInternal();                \
  if (!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                                                        \
  nsGlobalWindowOuter *outer = GetOuterWindowInternal();                \
  if (MOZ_LIKELY(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                                                        \
  nsGlobalWindowOuter *outer = GetOuterWindowInternal();                \
  if (!HasActiveDocument()) {                                           \
    NS_WARNING(outer ?                                                  \
               "Inner window does not have active document." :          \
               "No outer window available!");                           \
    return;                                                             \
  }                                                                     \
  outer->method args;                                                   \
  return;                                                               \
  PR_END_MACRO

#define DOM_TOUCH_LISTENER_ADDED "dom-touch-listener-added"
#define MEMORY_PRESSURE_OBSERVER_TOPIC "memory-pressure"

// Amount of time allowed between alert/prompt/confirm before enabling
// the stop dialog checkbox.
#define DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT 3 // 3 sec

// Maximum number of successive dialogs before we prompt users to disable
// dialogs for this window.
#define MAX_SUCCESSIVE_DIALOG_COUNT 5

// Idle fuzz time upper limit
#define MAX_IDLE_FUZZ_TIME_MS 90000

// Min idle notification time in seconds.
#define MIN_IDLE_NOTIFICATION_TIME_S 1

static LazyLogModule gDOMLeakPRLogInner("DOMLeakInner");

static bool                 gIdleObserversAPIFuzzTimeDisabled = false;
static FILE                *gDumpFile                         = nullptr;

nsGlobalWindowInner::InnerWindowByIdTable *nsGlobalWindowInner::sInnerWindowsById = nullptr;

bool nsGlobalWindowInner::sDragServiceDisabled = false;
bool nsGlobalWindowInner::sMouseDown = false;

/**
 * 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 StorageNotificationObserver
{
public:
  explicit nsGlobalWindowObserver(nsGlobalWindowInner* 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;
  }

  void
  ObserveStorageNotification(StorageEvent* aEvent,
                             const char16_t* aStorageType,
                             bool aPrivateBrowsing) override
  {
    if (mWindow) {
      mWindow->ObserveStorageNotification(aEvent, aStorageType,
                                          aPrivateBrowsing);
    }
  }

  nsIPrincipal*
  GetPrincipal() const override
  {
    return mWindow ? mWindow->GetPrincipal() : nullptr;
  }

  bool
  IsPrivateBrowsing() const override
  {
    return mWindow ? mWindow->IsPrivateBrowsing() : false;
  }

  nsIEventTarget*
  GetEventTarget() const override
  {
    return mWindow ? mWindow->EventTargetFor(TaskCategory::Other) : nullptr;
  }

private:
  ~nsGlobalWindowObserver() = default;

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

NS_IMPL_ISUPPORTS(nsGlobalWindowObserver, nsIObserver, nsIInterfaceRequestor)

class IdleRequestExecutor;

class IdleRequestExecutorTimeoutHandler final : public TimeoutHandler
{
public:
  explicit IdleRequestExecutorTimeoutHandler(IdleRequestExecutor* aExecutor)
    : mExecutor(aExecutor)
  {
  }

  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IdleRequestExecutorTimeoutHandler,
                                           TimeoutHandler)

  nsresult Call() override;

private:
  ~IdleRequestExecutorTimeoutHandler() override {}
  RefPtr<IdleRequestExecutor> mExecutor;
};

NS_IMPL_CYCLE_COLLECTION_INHERITED(IdleRequestExecutorTimeoutHandler, TimeoutHandler, mExecutor)

NS_IMPL_ADDREF_INHERITED(IdleRequestExecutorTimeoutHandler, TimeoutHandler)
NS_IMPL_RELEASE_INHERITED(IdleRequestExecutorTimeoutHandler, TimeoutHandler)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestExecutorTimeoutHandler)
NS_INTERFACE_MAP_END_INHERITING(TimeoutHandler)


class IdleRequestExecutor final : public nsIRunnable
                                , public nsICancelableRunnable
                                , public nsINamed
                                , public nsIIdleRunnable
{
public:
  explicit IdleRequestExecutor(nsGlobalWindowInner* aWindow)
    : mDispatched(false)
    , mDeadline(TimeStamp::Now())
    , mWindow(aWindow)
  {
    MOZ_DIAGNOSTIC_ASSERT(mWindow);

    mIdlePeriodLimit = { mDeadline, mWindow->LastIdleRequestHandle() };
    mDelayedExecutorDispatcher = new IdleRequestExecutorTimeoutHandler(this);
  }

  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IdleRequestExecutor, nsIRunnable)

  NS_DECL_NSIRUNNABLE
  NS_DECL_NSINAMED
  nsresult Cancel() override;
  void SetDeadline(TimeStamp aDeadline) override;

  bool IsCancelled() const { return !mWindow || mWindow->IsDying(); }
  // Checks if aRequest shouldn't execute in the current idle period
  // since it has been queued from a chained call to
  // requestIdleCallback from within a running idle callback.
  bool IneligibleForCurrentIdlePeriod(IdleRequest* aRequest) const
  {
    return aRequest->Handle() >= mIdlePeriodLimit.mLastRequestIdInIdlePeriod &&
           TimeStamp::Now() <= mIdlePeriodLimit.mEndOfIdlePeriod;
  }

  void MaybeUpdateIdlePeriodLimit();

  // Maybe dispatch the IdleRequestExecutor. MabyeDispatch will
  // schedule a delayed dispatch if the associated window is in the
  // background or if given a time to wait until dispatching.
  void MaybeDispatch(TimeStamp aDelayUntil = TimeStamp());
  void ScheduleDispatch();
private:
  struct IdlePeriodLimit
  {
    TimeStamp mEndOfIdlePeriod;
    uint32_t mLastRequestIdInIdlePeriod;
  };

  void DelayedDispatch(uint32_t aDelay);

  ~IdleRequestExecutor() override {}

  bool mDispatched;
  TimeStamp mDeadline;
  IdlePeriodLimit mIdlePeriodLimit;
  RefPtr<nsGlobalWindowInner> mWindow;
  // The timeout handler responsible for dispatching this executor in
  // the case of immediate dispatch to the idle queue isn't
  // desirable. This is used if we've dispatched all idle callbacks
  // that are allowed to run in the current idle period, or if the
  // associated window is currently in the background.
  nsCOMPtr<nsITimeoutHandler> mDelayedExecutorDispatcher;
  // If not Nothing() then this value is the handle to the currently
  // scheduled delayed executor dispatcher. This is needed to be able
  // to cancel the timeout handler in case of the executor being
  // cancelled.
  Maybe<int32_t> mDelayedExecutorHandle;
};

NS_IMPL_CYCLE_COLLECTION_CLASS(IdleRequestExecutor)

NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleRequestExecutor)
NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleRequestExecutor)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IdleRequestExecutor)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDelayedExecutorDispatcher)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IdleRequestExecutor)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDelayedExecutorDispatcher)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestExecutor)
  NS_INTERFACE_MAP_ENTRY(nsIRunnable)
  NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable)
  NS_INTERFACE_MAP_ENTRY(nsINamed)
  NS_INTERFACE_MAP_ENTRY(nsIIdleRunnable)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRunnable)
NS_INTERFACE_MAP_END

NS_IMETHODIMP
IdleRequestExecutor::GetName(nsACString& aName)
{
    aName.AssignASCII("IdleRequestExecutor");
    return NS_OK;
}

NS_IMETHODIMP
IdleRequestExecutor::Run()
{
  MOZ_ASSERT(NS_IsMainThread());

  mDispatched = false;
  if (mWindow) {
    return mWindow->ExecuteIdleRequest(mDeadline);
  }

  return NS_OK;
}

nsresult
IdleRequestExecutor::Cancel()
{
  MOZ_ASSERT(NS_IsMainThread());

  if (mDelayedExecutorHandle && mWindow) {
    mWindow->TimeoutManager().ClearTimeout(
      mDelayedExecutorHandle.value(),
      Timeout::Reason::eIdleCallbackTimeout);
  }

  mWindow = nullptr;
  return NS_OK;
}

void
IdleRequestExecutor::SetDeadline(TimeStamp aDeadline)
{
  MOZ_ASSERT(NS_IsMainThread());

  if (!mWindow) {
    return;
  }

  mDeadline = aDeadline;
}

void
IdleRequestExecutor::MaybeUpdateIdlePeriodLimit()
{
  if (TimeStamp::Now() > mIdlePeriodLimit.mEndOfIdlePeriod) {
    mIdlePeriodLimit = { mDeadline, mWindow->LastIdleRequestHandle() };
  }
}

void
IdleRequestExecutor::MaybeDispatch(TimeStamp aDelayUntil)
{
  // If we've already dispatched the executor we don't want to do it
  // again. Also, if we've called IdleRequestExecutor::Cancel mWindow
  // will be null, which indicates that we shouldn't dispatch this
  // executor either.
  if (mDispatched || IsCancelled()) {
    return;
  }

  mDispatched = true;

  nsPIDOMWindowOuter* outer = mWindow->GetOuterWindow();
  if (outer && outer->AsOuter()->IsBackground()) {
    // Set a timeout handler with a timeout of 0 ms to throttle idle
    // callback requests coming from a backround window using
    // background timeout throttling.
    DelayedDispatch(0);
    return;
  }

  TimeStamp now = TimeStamp::Now();
  if (!aDelayUntil || aDelayUntil < now) {
    ScheduleDispatch();
    return;
  }

  TimeDuration delay = aDelayUntil - now;
  DelayedDispatch(static_cast<uint32_t>(delay.ToMilliseconds()));
}

void
IdleRequestExecutor::ScheduleDispatch()
{
  MOZ_ASSERT(mWindow);
  mDelayedExecutorHandle = Nothing();
  RefPtr<IdleRequestExecutor> request = this;
  NS_IdleDispatchToCurrentThread(request.forget());
}

void
IdleRequestExecutor::DelayedDispatch(uint32_t aDelay)
{
  MOZ_ASSERT(mWindow);
  MOZ_ASSERT(mDelayedExecutorHandle.isNothing());
  int32_t handle;
  mWindow->TimeoutManager().SetTimeout(
    mDelayedExecutorDispatcher, aDelay, false, Timeout::Reason::eIdleCallbackTimeout, &handle);
  mDelayedExecutorHandle = Some(handle);
}

nsresult
IdleRequestExecutorTimeoutHandler::Call()
{
  if (!mExecutor->IsCancelled()) {
    mExecutor->ScheduleDispatch();
  }
  return NS_OK;
}

void
nsGlobalWindowInner::ScheduleIdleRequestDispatch()
{
  AssertIsOnMainThread();

  if (!mIdleRequestExecutor) {
    mIdleRequestExecutor = new IdleRequestExecutor(this);
  }

  mIdleRequestExecutor->MaybeDispatch();
}

void
nsGlobalWindowInner::SuspendIdleRequests()
{
  if (mIdleRequestExecutor) {
    mIdleRequestExecutor->Cancel();
    mIdleRequestExecutor = nullptr;
  }
}

void
nsGlobalWindowInner::ResumeIdleRequests()
{
  MOZ_ASSERT(!mIdleRequestExecutor);

  ScheduleIdleRequestDispatch();
}

void
nsGlobalWindowInner::RemoveIdleCallback(mozilla::dom::IdleRequest* aRequest)
{
  AssertIsOnMainThread();

  if (aRequest->HasTimeout()) {
    mTimeoutManager->ClearTimeout(aRequest->GetTimeoutHandle(),
                                  Timeout::Reason::eIdleCallbackTimeout);
  }

  aRequest->removeFrom(mIdleRequestCallbacks);
}

nsresult
nsGlobalWindowInner::RunIdleRequest(IdleRequest* aRequest,
                               DOMHighResTimeStamp aDeadline,
                               bool aDidTimeout)
{
  AssertIsOnMainThread();
  RefPtr<IdleRequest> request(aRequest);
  RemoveIdleCallback(request);
  return request->IdleRun(this, aDeadline, aDidTimeout);
}

nsresult
nsGlobalWindowInner::ExecuteIdleRequest(TimeStamp aDeadline)
{
  AssertIsOnMainThread();
  RefPtr<IdleRequest> request = mIdleRequestCallbacks.getFirst();

  if (!request) {
    // There are no more idle requests, so stop scheduling idle
    // request callbacks.
    return NS_OK;
  }

  // If the request that we're trying to execute has been queued
  // during the current idle period, then dispatch it again at the end
  // of the idle period.
  if (mIdleRequestExecutor->IneligibleForCurrentIdlePeriod(request)) {
    mIdleRequestExecutor->MaybeDispatch(aDeadline);
    return NS_OK;
  }

  DOMHighResTimeStamp deadline = 0.0;

  if (Performance* perf = GetPerformance()) {
    deadline = perf->GetDOMTiming()->TimeStampToDOMHighRes(aDeadline);
  }

  mIdleRequestExecutor->MaybeUpdateIdlePeriodLimit();
  nsresult result = RunIdleRequest(request, deadline, false);

  // Running the idle callback could've suspended the window, in which
  // case mIdleRequestExecutor will be null.
  if (mIdleRequestExecutor) {
    mIdleRequestExecutor->MaybeDispatch();
  }
  return result;
}

class IdleRequestTimeoutHandler final : public TimeoutHandler
{
public:
  IdleRequestTimeoutHandler(JSContext* aCx,
                            IdleRequest* aIdleRequest,
                            nsPIDOMWindowInner* aWindow)
    : TimeoutHandler(aCx)
    , mIdleRequest(aIdleRequest)
    , mWindow(aWindow)
  {
  }

  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IdleRequestTimeoutHandler,
                                           TimeoutHandler)

  nsresult Call() override
  {
    return nsGlobalWindowInner::Cast(mWindow)->RunIdleRequest(mIdleRequest, 0.0, true);
  }

private:
  ~IdleRequestTimeoutHandler() override {}

  RefPtr<IdleRequest> mIdleRequest;
  nsCOMPtr<nsPIDOMWindowInner> mWindow;
};

NS_IMPL_CYCLE_COLLECTION_INHERITED(IdleRequestTimeoutHandler,
                                   TimeoutHandler,
                                   mIdleRequest,
                                   mWindow)

NS_IMPL_ADDREF_INHERITED(IdleRequestTimeoutHandler, TimeoutHandler)
NS_IMPL_RELEASE_INHERITED(IdleRequestTimeoutHandler, TimeoutHandler)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestTimeoutHandler)
NS_INTERFACE_MAP_END_INHERITING(TimeoutHandler)

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

  if (IsDying()) {
   return 0;
  }

  uint32_t handle = mIdleRequestCallbackCounter++;

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

  if (aOptions.mTimeout.WasPassed()) {
    int32_t timeoutHandle;
    nsCOMPtr<nsITimeoutHandler> handler(new IdleRequestTimeoutHandler(aCx, request, this));

    nsresult rv = mTimeoutManager->SetTimeout(
        handler, aOptions.mTimeout.Value(), false,
        Timeout::Reason::eIdleCallbackTimeout, &timeoutHandle);

    if (NS_WARN_IF(NS_FAILED(rv))) {
      return 0;
    }

    request->SetTimeoutHandle(timeoutHandle);
  }

  mIdleRequestCallbacks.insertBack(request);

  if (!IsSuspended()) {
    ScheduleIdleRequestDispatch();
  }

  return handle;
}

void
nsGlobalWindowInner::CancelIdleCallback(uint32_t aHandle)
{
  for (IdleRequest* r : mIdleRequestCallbacks) {
    if (r->Handle() == aHandle) {
      RemoveIdleCallback(r);
      break;
    }
  }
}

void
nsGlobalWindowInner::DisableIdleCallbackRequests()
{
  if (mIdleRequestExecutor) {
    mIdleRequestExecutor->Cancel();
    mIdleRequestExecutor = nullptr;
  }

  while (!mIdleRequestCallbacks.isEmpty()) {
    RefPtr<IdleRequest> request = mIdleRequestCallbacks.getFirst();
    RemoveIdleCallback(request);
  }
}

bool
nsGlobalWindowInner::IsBackgroundInternal() const
{
  return !mOuterWindow || mOuterWindow->IsBackground();
}

class PromiseDocumentFlushedResolver final {
public:
  PromiseDocumentFlushedResolver(Promise* aPromise,
                                 PromiseDocumentFlushedCallback& aCallback)
  : mPromise(aPromise)
  , mCallback(&aCallback)
  {
  }

  virtual ~PromiseDocumentFlushedResolver() = default;

  void Call()
  {
    ErrorResult error;
    JS::Rooted<JS::Value> returnVal(RootingCx());
    mCallback->Call(&returnVal, error);

    if (error.Failed()) {
      mPromise->MaybeReject(error);
    } else {
      mPromise->MaybeResolve(returnVal);
    }
  }

  void Cancel()
  {
    mPromise->MaybeReject(NS_ERROR_ABORT);
  }

  RefPtr<Promise> mPromise;
  RefPtr<PromiseDocumentFlushedCallback> mCallback;
};

//*****************************************************************************
//***    nsGlobalWindowInner: Object Management
//*****************************************************************************

nsGlobalWindowInner::nsGlobalWindowInner(nsGlobalWindowOuter *aOuterWindow)
  : nsPIDOMWindowInner(aOuterWindow->AsOuter()),
    mIdleFuzzFactor(0),
    mIdleCallbackIndex(-1),
    mCurrentlyIdle(false),
    mAddActiveEventFuzzTime(true),
    mWasOffline(false),
    mHasHadSlowScript(false),
    mNotifyIdleObserversIdleOnThaw(false),
    mNotifyIdleObserversActiveOnThaw(false),
    mIsChrome(false),
    mCleanMessageManager(false),
    mNeedsFocus(true),
    mHasFocus(false),
    mShowFocusRingForContent(false),
    mFocusByKeyOccurred(false),
    mHasGamepad(false),
    mHasVREvents(false),
    mHasVRDisplayActivateEvents(false),
    mHasSeenGamepadInput(false),
    mSuspendDepth(0),
    mFreezeDepth(0),
    mFocusMethod(0),
    mSerial(0),
    mIdleRequestCallbackCounter(1),
    mIdleRequestExecutor(nullptr),
    mDialogAbuseCount(0),
    mAreDialogsEnabled(true),
    mObservingDidRefresh(false),
    mIteratingDocumentFlushedResolvers(false),
    mCanSkipCCGeneration(0),
    mBeforeUnloadListenerCount(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);

    mTimeoutManager =
      MakeUnique<mozilla::dom::TimeoutManager>(*nsGlobalWindowInner::Cast(AsInner()));

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

        os->AddObserver(mObserver, MEMORY_PRESSURE_OBSERVER_TOPIC, false);

        if (aOuterWindow->IsTopLevelWindow()) {
          os->AddObserver(mObserver, "clear-site-data-reload-needed", false);
        }
      }

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

      // Watch for storage notifications so we can fire storage events.
      RefPtr<StorageNotifierService> sns =
        StorageNotifierService::GetOrCreate();
      if (sns) {
        sns->Register(mObserver);
      }
    }
  } else {
    // |this| is an outer window. Outer windows start out frozen and
    // remain frozen until they get an inner window.
    MOZ_ASSERT(IsFrozen());
  }

  if (XRE_IsContentProcess()) {
    nsCOMPtr<nsIDocShell> docShell = GetDocShell();
    if (docShell) {
      mTabChild = docShell->GetTabChild();
    }
  }

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

  mSerial = nsContentUtils::InnerOrOuterWindowCreated();

  static bool sFirstTime = true;
  if (sFirstTime) {
    sFirstTime = false;
    TimeoutManager::Initialize();
    Preferences::AddBoolVarCache(&gIdleObserversAPIFuzzTimeDisabled,
                                 "dom.idle-observers-api.fuzz_time.disabled",
                                 false);
  }

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

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

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

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

#ifdef DEBUG

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

#endif // DEBUG

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

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

  sInnerWindowsById = new InnerWindowByIdTable();
}

nsGlobalWindowInner::~nsGlobalWindowInner()
{
  AssertIsOnMainThread();

  if (IsChromeWindow()) {
    MOZ_ASSERT(mCleanMessageManager,
              "chrome windows may always disconnect the msg manager");

    DisconnectAndClearGroupMessageManagers();

    if (mChromeFields.mMessageManager) {
      static_cast<nsFrameMessageManager *>(
        mChromeFields.mMessageManager.get())->Disconnect();
    }

    mCleanMessageManager = false;
  }

  // In most cases this should already have been called, but call it again
  // here to catch any corner cases.
  FreeInnerObjects();

  if (sInnerWindowsById) {
    MOZ_ASSERT(sInnerWindowsById->Get(mWindowID),
                "This window should be in the hash table");
    sInnerWindowsById->Remove(mWindowID);
  }

  nsContentUtils::InnerOrOuterWindowDestroyed();

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

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

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

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

  // 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.
  nsGlobalWindowOuter *outer = GetOuterWindowInternal();
  if (outer) {
    outer->MaybeClearInnerWindow(this);
  }

  // We don't have to leave the tab group if we are an inner window.

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

  nsLayoutStatics::Release();
}

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

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

  delete sInnerWindowsById;
  sInnerWindowsById = nullptr;
}

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

void
nsGlobalWindowInner::FreeInnerObjects()
{
  if (IsDying()) {
    return;
  }
  StartDying();

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

  // Kill all of the workers for this window.
  CancelWorkersForWindow(this);

  if (mTimeoutManager) {
    mTimeoutManager->ClearAllTimeouts();
  }

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

  mIdleObservers.Clear();

  DisableIdleCallbackRequests();

  mChromeEventHandler = nullptr;

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

  mHistory = nullptr;

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

  mScreen = nullptr;

#if defined(MOZ_WIDGET_ANDROID)
  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(false);
    }

    if (mObservingDidRefresh) {
      nsIPresShell* shell = mDoc->GetShell();
      if (shell) {
        Unused << shell->RemovePostRefreshObserver(this);
      }
    }
  }

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

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

  if (mIndexedDB) {
    mIndexedDB->DisconnectFromWindow(this);
    mIndexedDB = nullptr;
  }

  UnlinkHostObjectURIs();

  NotifyWindowIDDestroyed("inner-window-destroyed");

  CleanupCachedXBLHandlers();

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

  DisableGamepadUpdates();
  mHasGamepad = false;
  mGamepads.Clear();
  DisableVRUpdates();
  mHasVREvents = false;
  mHasVRDisplayActivateEvents = false;
  mVRDisplays.Clear();

  // This breaks a cycle between the window and the ClientSource object.
  mClientSource.reset();

  if (mTabChild) {
    // Remove any remaining listeners, and reset mBeforeUnloadListenerCount.
    for (int i = 0; i < mBeforeUnloadListenerCount; ++i) {
      mTabChild->BeforeUnloadRemoved();
    }
    mBeforeUnloadListenerCount = 0;
  }

  // If we have any promiseDocumentFlushed callbacks, fire them now so
  // that the Promises can resolve.
  CallDocumentFlushedResolvers();
  mObservingDidRefresh = false;

  DisconnectEventTargetObjects();

  if (mObserver) {
    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
    if (os) {
      os->RemoveObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
      os->RemoveObserver(mObserver, MEMORY_PRESSURE_OBSERVER_TOPIC);

      if (GetOuterWindowInternal() &&
          GetOuterWindowInternal()->IsTopLevelWindow()) {
        os->RemoveObserver(mObserver, "clear-site-data-reload-needed");
      }
    }

    RefPtr<StorageNotifierService> sns = StorageNotifierService::GetOrCreate();
    if (sns) {
     sns->Unregister(mObserver);
    }

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

  mMenubar = nullptr;
  mToolbar = nullptr;
  mLocationbar = nullptr;
  mPersonalbar = nullptr;
  mStatusbar = nullptr;
  mScrollbars = nullptr;

  mConsole = nullptr;

  mAudioWorklet = nullptr;
  mPaintWorklet = nullptr;

  mExternal = nullptr;
  mInstallTrigger = nullptr;

  mPerformance = nullptr;

#ifdef MOZ_WEBSPEECH
  mSpeechSynthesis = nullptr;
#endif

  mParentTarget = nullptr;

  if (mCleanMessageManager) {
    MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned");
    if (mChromeFields.mMessageManager) {
      mChromeFields.mMessageManager->Disconnect();
    }
  }

  mIntlUtils = nullptr;
}

//*****************************************************************************
// nsGlobalWindowInner::nsISupports
//*****************************************************************************

// QueryInterface implementation for nsGlobalWindowInner
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGlobalWindowInner)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, EventTarget)
  NS_INTERFACE_MAP_ENTRY(nsIDOMWindow)
  NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
  NS_INTERFACE_MAP_ENTRY(nsIScriptGlobalObject)
  NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
  NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget)
  NS_INTERFACE_MAP_ENTRY(nsPIDOMWindowInner)
  NS_INTERFACE_MAP_ENTRY(mozIDOMWindow)
  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDOMChromeWindow, IsChromeWindow())
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
  NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_END


NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGlobalWindowInner)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGlobalWindowInner)

NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGlobalWindowInner)
  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();
    }
    if (tmp->mTimeoutManager) {
      tmp->mTimeoutManager->UnmarkGrayTimers();
    }
    return true;
  }
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END

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

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

NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalWindowInner)

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

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNavigator)

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance)

#ifdef MOZ_WEBSPEECH
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSpeechSynthesis)
#endif

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOuterWindow)

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopInnerWindow)

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)

  if (tmp->mTimeoutManager) {
    tmp->mTimeoutManager->ForEachUnorderedTimeout([&cb](Timeout* timeout) {
      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(mIndexedDB)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTabChild)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleService)

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

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleObservers)

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClientSource)

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepads)

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

  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(mAudioWorklet)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPaintWorklet)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExternal)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInstallTrigger)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIntlUtils)

  tmp->TraverseHostObjectURIs(cb);

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mMessageManager)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mGroupMessageManagers)

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPromises)

  for (size_t i = 0; i < tmp->mDocumentFlushedResolvers.Length(); i++) {
    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentFlushedResolvers[i]->mPromise);
    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentFlushedResolvers[i]->mCallback);
  }

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowInner)
  tmp->CleanupCachedXBLHandlers();

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigator)

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance)


#ifdef MOZ_WEBSPEECH
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSpeechSynthesis)
#endif

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

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

  // Here the Timeouts list would've been unlinked, but we rely on
  // that Timeout objects have been traced and will remove themselves
  // while unlinking.

  tmp->UpdateTopInnerWindow();
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTopInnerWindow)

  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)
  }
  if (tmp->mIndexedDB) {
    tmp->mIndexedDB->DisconnectFromWindow(tmp);
    NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexedDB)
  }
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTabChild)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleService)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleObservers)

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mGamepads)

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

  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(mAudioWorklet)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPaintWorklet)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mExternal)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mInstallTrigger)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntlUtils)

  tmp->UnlinkHostObjectURIs();

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleRequestExecutor)

  // Here the IdleRequest list would've been unlinked, but we rely on
  // that IdleRequest objects have been traced and will remove
  // themselves while unlinking.

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mClientSource)

  if (tmp->IsChromeWindow()) {
    if (tmp->mChromeFields.mMessageManager) {
      static_cast<nsFrameMessageManager*>(
        tmp->mChromeFields.mMessageManager.get())->Disconnect();
      NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeFields.mMessageManager)
    }
    tmp->DisconnectAndClearGroupMessageManagers();
    NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeFields.mGroupMessageManagers)
  }

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPromises)
  for (size_t i = 0; i < tmp->mDocumentFlushedResolvers.Length(); i++) {
    NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentFlushedResolvers[i]->mPromise);
    NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentFlushedResolvers[i]->mCallback);
  }
  tmp->mDocumentFlushedResolvers.Clear();

  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

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

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsGlobalWindowInner)
  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
nsGlobalWindowInner::IsBlackForCC(bool aTracingNeeded)
{
  if (!nsCCUncollectableMarker::sGeneration) {
    return false;
  }

  return (nsCCUncollectableMarker::InGeneration(GetMarkedCCGeneration()) ||
          HasKnownLiveWrapper()) &&
         (!aTracingNeeded ||
          HasNothingToTrace(ToSupports(this)));
}

//*****************************************************************************
// nsGlobalWindowInner::nsIScriptGlobalObject
//*****************************************************************************

nsresult
nsGlobalWindowInner::EnsureScriptEnvironment()
{
  // NOTE: We can't use FORWARD_TO_OUTER here because we don't want to fail if
  // we're called on an inactive inner window.
  nsGlobalWindowOuter* outer = GetOuterWindowInternal();
  if (!outer) {
    NS_WARNING("No outer window available!");
    return NS_ERROR_FAILURE;
  }
  return outer->EnsureScriptEnvironment();
}

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

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

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

PopupControlState
nsGlobalWindowInner::GetPopupControlState() const
{
  return nsContentUtils::GetPopupControlState();
}

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

  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() != this) {
    return NS_ERROR_NOT_AVAILABLE;
  }

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

void
nsGlobalWindowInner::InnerSetNewDocument(JSContext* aCx, nsIDocument* aDocument)
{
  MOZ_ASSERT(aDocument);

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

  mDoc = aDocument;
  ClearDocumentDependentSlots(aCx);
  mFocusedElement = 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;
}

nsresult
nsGlobalWindowInner::EnsureClientSource()
{
  MOZ_DIAGNOSTIC_ASSERT(mDoc);

  bool newClientSource = false;

  // Get the load info for the document if we performed a load.  Be careful not
  // to look at local URLs, though. Local URLs are those that have a scheme of:
  //  * about:
  //  * data:
  //  * blob:
  // We also do an additional check here so that we only treat about:blank
  // and about:srcdoc as local URLs.  Other internal firefox about: URLs should
  // not be treated this way.
  nsCOMPtr<nsILoadInfo> loadInfo;
  nsCOMPtr<nsIChannel> channel = mDoc->GetChannel();
  if (channel) {
    nsCOMPtr<nsIURI> uri;
    Unused << channel->GetURI(getter_AddRefs(uri));

    bool ignoreLoadInfo = false;

    // Note, this is mostly copied from NS_IsAboutBlank().  Its duplicated
    // here so we can efficiently check about:srcdoc as well.
    bool isAbout = false;
    if (NS_SUCCEEDED(uri->SchemeIs("about", &isAbout)) && isAbout) {
      nsCString spec = uri->GetSpecOrDefault();
      ignoreLoadInfo = spec.EqualsLiteral("about:blank") ||
                       spec.EqualsLiteral("about:srcdoc");
    } else {
      // Its not an about: URL, so now check for our other URL types.
      bool isData = false;
      bool isBlob = false;
      ignoreLoadInfo = (NS_SUCCEEDED(uri->SchemeIs("data", &isData)) && isData) ||
                       (NS_SUCCEEDED(uri->SchemeIs("blob", &isBlob)) && isBlob);
    }

    if (!ignoreLoadInfo) {
      loadInfo = channel->GetLoadInfo();
    }
  }

  // Take the initial client source from the docshell immediately.  Even if we
  // don't end up using it here we should consume it.
  UniquePtr<ClientSource> initialClientSource;
  nsIDocShell* docshell = GetDocShell();
  if (docshell) {
    initialClientSource = docshell->TakeInitialClientSource();
  }

  // Try to get the reserved client from the LoadInfo.  A Client is
  // reserved at the start of the channel load if there is not an
  // initial about:blank document that will be reused.  It is also
  // created if the channel load encounters a cross-origin redirect.
  if (loadInfo) {
    UniquePtr<ClientSource> reservedClient = loadInfo->TakeReservedClientSource();
    if (reservedClient) {
      mClientSource.reset();
      mClientSource = std::move(reservedClient);
      newClientSource = true;
    }
  }

  // We don't have a LoadInfo reserved client, but maybe we should
  // be inheriting an initial one from the docshell.  This means
  // that the docshell started the channel load before creating the
  // initial about:blank document.  This is an optimization, though,
  // and it created an initial Client as a placeholder for the document.
  // In this case we want to inherit this placeholder Client here.
  if (!mClientSource) {
    mClientSource = std::move(initialClientSource);
    if (mClientSource) {
      newClientSource = true;
    }
  }

  // Verify the final ClientSource principal matches the final document
  // principal.  The ClientChannelHelper handles things like network
  // redirects, but there are other ways the document principal can change.
  // For example, if something sets the nsIChannel.owner property, then
  // the final channel principal can be anything.  Unfortunately there is
  // no good way to detect this until after the channel completes loading.
  //
  // For now we handle this just by reseting the ClientSource.  This will
  // result in a new ClientSource with the correct principal being created.
  // To APIs like ServiceWorker and Clients API it will look like there was
  // an initial content page created that was then immediately replaced.
  // This is pretty close to what we are actually doing.
  if (mClientSource) {
    nsCOMPtr<nsIPrincipal> clientPrincipal(mClientSource->Info().GetPrincipal());
    if (!clientPrincipal || !clientPrincipal->Equals(mDoc->NodePrincipal())) {
      mClientSource.reset();
    }
  }

  // If we don't have a reserved client or an initial client, then create
  // one now.  This can happen in certain cases where we avoid preallocating
  // the client in the docshell.  This mainly occurs in situations where
  // the principal is not clearly inherited from the parent; e.g. sandboxed
  // iframes, window.open(), etc.
  //
  // We also do this late ClientSource creation if the final document ended
  // up with a different principal.
  //
  // TODO: We may not be marking initial about:blank documents created
  //       this way as controlled by a service worker properly.  The
  //       controller should be coming from the same place as the inheritted
  //       principal.  We do this in docshell, but as mentioned we aren't
  //       smart enough to handle all cases yet.  For example, a
  //       window.open() with new URL should inherit the controller from
  //       the opener, but we probably don't handle that yet.
  if (!mClientSource) {
    mClientSource = ClientManager::CreateSource(ClientType::Window,
                                                EventTargetFor(TaskCategory::Other),
                                                mDoc->NodePrincipal());
    MOZ_DIAGNOSTIC_ASSERT(mClientSource);
    newClientSource = true;

    // Note, we don't apply the loadinfo controller below if we create
    // the ClientSource here.
  }

  // The load may have started controlling the Client as well.  If
  // so, mark it as controlled immediately here.  The actor may
  // or may not have been notified by the parent side about being
  // controlled yet.
  //
  // Note: We should be careful not to control a client that was created late.
  //       These clients were not seen by the ServiceWorkerManager when it
  //       marked the LoadInfo controlled and it won't know about them.  Its
  //       also possible we are creating the client late due to the final
  //       principal changing and these clients should definitely not be
  //       controlled by a service worker with a different principal.
  else if (loadInfo) {
    const Maybe<ServiceWorkerDescriptor> controller = loadInfo->GetController();
    if (controller.isSome()) {
      mClientSource->SetController(controller.ref());
    }

    // We also have to handle the case where te initial about:blank is
    // controlled due to inheritting the service worker from its parent,
    // but the actual nsIChannel load is not covered by any service worker.
    // In this case we want the final page to be uncontrolled.  There is
    // an open spec issue about how exactly this should be handled, but for
    // now we just force creation of a new ClientSource to clear the
    // controller.
    //
    //  https://github.com/w3c/ServiceWorker/issues/1232
    //
    else if (mClientSource->GetController().isSome()) {
      mClientSource.reset();
      mClientSource =
        ClientManager::CreateSource(ClientType::Window,
                                    EventTargetFor(TaskCategory::Other),
                                    mDoc->NodePrincipal());
      MOZ_DIAGNOSTIC_ASSERT(mClientSource);
      newClientSource = true;
    }
  }

  // Its possible that we got a client just after being frozen in
  // the bfcache.  In that case freeze the client immediately.
  if (newClientSource && IsFrozen()) {
    mClientSource->Freeze();
  }

  return NS_OK;
}

nsresult
nsGlobalWindowInner::ExecutionReady()
{
  nsresult rv = EnsureClientSource();
  NS_ENSURE_SUCCESS(rv, rv);

  rv = mClientSource->WindowExecutionReady(AsInner());
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

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

void
nsGlobalWindowInner::UpdateParentTarget()
{
  // NOTE: This method is identical to
  // nsGlobalWindowOuter::UpdateParentTarget(). IF YOU UPDATE THIS METHOD,
  // UPDATE THE OTHER ONE TOO!

  // 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 =
    nsContentUtils::TryGetTabChildGlobalAsEventTarget(frameElement);

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

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

  if (!eventTarget) {
    eventTarget = mChromeEventHandler;
  }

  mParentTarget = eventTarget;
}

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

void
nsGlobalWindowInner::GetEventTargetParent(EventChainPreVisitor& aVisitor)
{
  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()) {
    sMouseDown = true;
  } else if ((msg == eMouseUp || msg == eDragEnd) &&
             aVisitor.mEvent->IsTrusted()) {
    sMouseDown = false;
    if (sDragServiceDisabled) {
      nsCOMPtr<nsIDragService> ds =
        do_GetService("@mozilla.org/widget/dragservice;1");
      if (ds) {
        sDragServiceDisabled = false;
        ds->Unsuppress();
      }
    }
  }

  aVisitor.SetParentTarget(GetParentTarget(), true);

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

bool
nsGlobalWindowInner::DialogsAreBeingAbused()
{
  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;
}

void
nsGlobalWindowInner::DisableDialogs()
{
  FORWARD_TO_OUTER_VOID(DisableDialogs, ());
}

void
nsGlobalWindowInner::EnableDialogs()
{
  FORWARD_TO_OUTER_VOID(EnableDialogs, ());
}

nsresult
nsGlobalWindowInner::PostHandleEvent(EventChainPostVisitor& aVisitor)
{
  // 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. */
  RefPtr<EventTarget> 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()) {

    // If any VR display presentation is active at unload, the next page
    // will receive a vrdisplayactive event to indicate that it should
    // immediately begin vr presentation. This should occur when navigating
    // forwards, navigating backwards, and on page reload.
    for (const auto& display : mVRDisplays) {
      if (display->IsPresenting()) {
        // Save this VR display ID to trigger vrdisplayactivate event
        // after the next load event.
        nsGlobalWindowOuter* outer = GetOuterWindowInternal();
        if (outer) {
          outer->SetAutoActivateVRDisplayID(display->DisplayId());
        }

        // XXX The WebVR 1.1 spec does not define which of multiple VR
        // presenting VR displays will be chosen during navigation.
        // As the underlying platform VR API's currently only allow a single
        // VR display, it is safe to choose the first VR display for now.
        break;
      }
    }
    // 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;

    mTimeoutManager->OnDocumentLoaded();

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

    if (mVREventObserver) {
      mVREventObserver->NotifyAfterLoad();
    }

    uint32_t autoActivateVRDisplayID = 0;
    nsGlobalWindowOuter* outer = GetOuterWindowInternal();
    if (outer) {
      autoActivateVRDisplayID = outer->GetAutoActivateVRDisplayID();
    }
    if (autoActivateVRDisplayID) {
      DispatchVRDisplayActivate(autoActivateVRDisplayID,
                                VRDisplayEventReason::Navigation);
    }
  }

  return NS_OK;
}

nsresult
nsGlobalWindowInner::DefineArgumentsProperty(nsIArray *aArguments)
{
  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);
}

//*****************************************************************************
// nsGlobalWindowInner::nsIScriptObjectPrincipal
//*****************************************************************************

nsIPrincipal*
nsGlobalWindowInner::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;
}

//*****************************************************************************
// nsGlobalWindowInner::nsIDOMWindow
//*****************************************************************************

bool
nsPIDOMWindowInner::AddAudioContext(AudioContext* aAudioContext)
{
  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)
{
  mAudioContexts.RemoveElement(aAudioContext);
}

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

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

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

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

Navigator*
nsPIDOMWindowInner::Navigator()
{
  if (!mNavigator) {
    mNavigator = new mozilla::dom::Navigator(this);
  }

  return mNavigator;
}

nsScreen*
nsGlobalWindowInner::GetScreen(ErrorResult& aError)
{
  if (!mScreen) {
    mScreen = nsScreen::Create(this);
    if (!mScreen) {
      aError.Throw(NS_ERROR_UNEXPECTED);
      return nullptr;
    }
  }

  return mScreen;
}

nsHistory*
nsGlobalWindowInner::GetHistory(ErrorResult& aError)
{
  if (!mHistory) {
    mHistory = new nsHistory(this);
  }

  return mHistory;
}

CustomElementRegistry*
nsGlobalWindowInner::CustomElements()
{
  if (!mCustomElements) {
    mCustomElements = new CustomElementRegistry(this);
  }

  return mCustomElements;
}

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

void
nsPIDOMWindowInner::CreatePerformanceObjectIfNeeded()
{
  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) {
    mPerformance = Performance::CreateForMainThread(this, mDoc->NodePrincipal(), timing, timedChannel);
  }
}

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

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

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

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

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

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

Maybe<ClientInfo>
nsPIDOMWindowInner::GetClientInfo() const
{
  return nsGlobalWindowInner::Cast(this)->GetClientInfo();
}

Maybe<ClientState>
nsPIDOMWindowInner::GetClientState() const
{
  return nsGlobalWindowInner::Cast(this)->GetClientState();
}

Maybe<ServiceWorkerDescriptor>
nsPIDOMWindowInner::GetController() const
{
  return nsGlobalWindowInner::Cast(this)->GetController();
}

RefPtr<mozilla::dom::ServiceWorker>
nsPIDOMWindowInner::GetOrCreateServiceWorker(const mozilla::dom::ServiceWorkerDescriptor& aDescriptor)
{
  return nsGlobalWindowInner::Cast(this)->GetOrCreateServiceWorker(aDescriptor);
}

void
nsPIDOMWindowInner::NoteCalledRegisterForServiceWorkerScope(const nsACString& aScope)
{
  nsGlobalWindowInner::Cast(this)->NoteCalledRegisterForServiceWorkerScope(aScope);
}

void
nsPIDOMWindowInner::NoteDOMContentLoaded()
{
  nsGlobalWindowInner::Cast(this)->NoteDOMContentLoaded();
}

bool
nsGlobalWindowInner::ShouldReportForServiceWorkerScope(const nsAString& aScope)
{
  bool result = false;

  nsPIDOMWindowOuter* topOuter = GetScriptableTop();
  NS_ENSURE_TRUE(topOuter, false);

  nsGlobalWindowInner* topInner =
    nsGlobalWindowInner::Cast(topOuter->GetCurrentInnerWindow());
  NS_ENSURE_TRUE(topInner, false);

  topInner->ShouldReportForServiceWorkerScopeInternal(NS_ConvertUTF16toUTF8(aScope),
                                                      &result);
  return result;
}

already_AddRefed<InstallTriggerImpl>
nsGlobalWindowInner::GetInstallTrigger()
{
  if (!mInstallTrigger) {
    JS::Rooted<JSObject*> jsImplObj(RootingCx());
    ErrorResult rv;
    ConstructJSImplementation("@mozilla.org/addons/installtrigger;1", this,
                              &jsImplObj, rv);
    if (rv.Failed()) {
      rv.SuppressException();
      return nullptr;
    }
    mInstallTrigger = new InstallTriggerImpl(jsImplObj, this);
  }

  return do_AddRef(mInstallTrigger);
}

nsGlobalWindowInner::CallState
nsGlobalWindowInner::ShouldReportForServiceWorkerScopeInternal(const nsACString& aScope,
                                                               bool* aResultOut)
{
  MOZ_DIAGNOSTIC_ASSERT(aResultOut);

  // First check to see if this window is controlled.  If so, then we have
  // found a match and are done.
  const Maybe<ServiceWorkerDescriptor> swd = GetController();
  if (swd.isSome() && swd.ref().Scope() == aScope) {
    *aResultOut = true;
    return CallState::Stop;
  }

  // Next, check to see if this window has called navigator.serviceWorker.register()
  // for this scope.  If so, then treat this as a match so console reports
  // appear in the devtools console.
  if (mClientSource && mClientSource->CalledRegisterForServiceWorkerScope(aScope)) {
    *aResultOut = true;
    return CallState::Stop;
  }

  // Finally check the current docshell nsILoadGroup to see if there are any
  // outstanding navigation requests.  If so, match the scope against the
  // channel's URL.  We want to show console reports during the FetchEvent
  // intercepting the navigation itself.
  nsCOMPtr<nsIDocumentLoader> loader(do_QueryInterface(GetDocShell()));
  if (loader) {
    nsCOMPtr<nsILoadGroup> loadgroup;
    Unused << loader->GetLoadGroup(getter_AddRefs(loadgroup));
    if (loadgroup) {
      nsCOMPtr<nsISimpleEnumerator> iter;
      Unused << loadgroup->GetRequests(getter_AddRefs(iter));
      if (iter) {
        nsCOMPtr<nsISupports> tmp;
        bool hasMore = true;
        // Check each network request in the load group.
        while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
          iter->GetNext(getter_AddRefs(tmp));
          nsCOMPtr<nsIChannel> loadingChannel(do_QueryInterface(tmp));
          // Ignore subresource requests.  Logging for a subresource
          // FetchEvent should be handled above since the client is
          // already controlled.
          if (!loadingChannel ||
              !nsContentUtils::IsNonSubresourceRequest(loadingChannel)) {
            continue;
          }
          nsCOMPtr<nsIURI> loadingURL;
          Unused << loadingChannel->GetURI(getter_AddRefs(loadingURL));
          if (!loadingURL) {
            continue;
          }
          nsAutoCString loadingSpec;
          Unused << loadingURL->GetSpec(loadingSpec);
          // Perform a simple substring comparison to match the scope
          // against the channel URL.
          if (StringBeginsWith(loadingSpec, aScope)) {
            *aResultOut = true;
            return CallState::Stop;
          }
        }
      }
    }
  }

  // The current window doesn't care about this service worker, but maybe
  // one of our child frames does.
  return CallOnChildren(&nsGlobalWindowInner::ShouldReportForServiceWorkerScopeInternal,
                        aScope, aResultOut);
}

void
nsGlobalWindowInner::NoteCalledRegisterForServiceWorkerScope(const nsACString& aScope)
{
  if (!mClientSource) {
    return;
  }

  mClientSource->NoteCalledRegisterForServiceWorkerScope(aScope);
}

void
nsGlobalWindowInner::NoteDOMContentLoaded()
{
  if (!mClientSource) {
    return;
  }

  mClientSource->NoteDOMContentLoaded();
}

void
nsGlobalWindowInner::MigrateStateForDocumentOpen(nsGlobalWindowInner* aOldInner)
{
  MOZ_DIAGNOSTIC_ASSERT(aOldInner);
  MOZ_DIAGNOSTIC_ASSERT(aOldInner != this);
  MOZ_DIAGNOSTIC_ASSERT(mDoc);

  // Rebind DETH objects to the new global created by document.open().
  // XXX: Is this correct?  We should consider if the spec and our
  //      implementation should change to match other browsers by
  //      just reusing the current window.  (Bug 1449992)
  aOldInner->ForEachEventTargetObject(
    [&] (DOMEventTargetHelper* aDETH, bool* aDoneOut) {
      aDETH->BindToOwner(this->AsInner());
    });

  // Move the old Performance object from the old window to the new window.
  // The Performance object was also rebound in the DETH loop above.
  mPerformance = aOldInner->mPerformance.forget();

  if (aOldInner->mIndexedDB) {
    aOldInner->mIndexedDB->RebindToNewWindow(this);
    mIndexedDB = aOldInner->mIndexedDB.forget();
  }
}

void
nsGlobalWindowInner::UpdateTopInnerWindow()
{
  if (IsTopInnerWindow() || !mTopInnerWindow) {
    return;
  }

  mTopInnerWindow->UpdateWebSocketCount(-(int32_t)mNumOfOpenWebSockets);
}

void
nsPIDOMWindowInner::AddPeerConnection()
{
  MOZ_ASSERT(NS_IsMainThread());
  mTopInnerWindow ? mTopInnerWindow->mActivePeerConnections++
                  : mActivePeerConnections++;
}

void
nsPIDOMWindowInner::RemovePeerConnection()
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(mTopInnerWindow ? mTopInnerWindow->mActivePeerConnections
                             : mActivePeerConnections);

  mTopInnerWindow ? mTopInnerWindow->mActivePeerConnections--
                  : mActivePeerConnections--;
}

bool
nsPIDOMWindowInner::HasActivePeerConnections()
{
  MOZ_ASSERT(NS_IsMainThread());
  return mTopInnerWindow ? mTopInnerWindow->mActivePeerConnections
                         : mActivePeerConnections;
}

bool
nsPIDOMWindowInner::IsPlayingAudio()
{
  for (uint32_t i = 0; i < mAudioContexts.Length(); i++) {
    if (mAudioContexts[i]->IsRunning()) {
      return true;
    }
  }
  RefPtr<AudioChannelService> acs = AudioChannelService::Get();
  if (!acs) {
    return false;
  }
  auto outer = GetOuterWindow();
  if (!outer) {
    // We've been unlinked and are about to die.  Not a good time to pretend to
    // be playing audio.
    return false;
  }
  return acs->IsWindowActive(outer);
}

bool
nsPIDOMWindowInner::IsDocumentLoaded() const
{
  return mIsDocumentLoaded;
}

mozilla::dom::TimeoutManager&
nsPIDOMWindowInner::TimeoutManager()
{
  return *mTimeoutManager;
}

bool
nsPIDOMWindowInner::IsRunningTimeout()
{
  return TimeoutManager().IsRunningTimeout();
}

void
nsPIDOMWindowInner::TryToCacheTopInnerWindow()
{
  if (mHasTriedToCacheTopInnerWindow) {
    return;
  }

  nsGlobalWindowInner* window = nsGlobalWindowInner::Cast(this);

  MOZ_ASSERT(!window->IsDying());

  mHasTriedToCacheTopInnerWindow = true;

  MOZ_ASSERT(window);

  if (nsCOMPtr<nsPIDOMWindowOuter> topOutter = window->GetScriptableTop()) {
    mTopInnerWindow = topOutter->GetCurrentInnerWindow();
  }
}

void
nsPIDOMWindowInner::UpdateActiveIndexedDBTransactionCount(int32_t aDelta)
{
  MOZ_ASSERT(NS_IsMainThread());

  if (aDelta == 0) {
    return;
  }

  TabGroup()->IndexedDBTransactionCounter() += aDelta;
}

void
nsPIDOMWindowInner::UpdateActiveIndexedDBDatabaseCount(int32_t aDelta)
{
  MOZ_ASSERT(NS_IsMainThread());

  if (aDelta == 0) {
    return;
  }

  // We count databases but not transactions because only active databases
  // could block throttling.
  uint32_t& counter = mTopInnerWindow ?
    mTopInnerWindow->mNumOfIndexedDBDatabases : mNumOfIndexedDBDatabases;

  counter+= aDelta;

  TabGroup()->IndexedDBDatabaseCounter() += aDelta;
}

bool
nsPIDOMWindowInner::HasActiveIndexedDBDatabases()
{
  MOZ_ASSERT(NS_IsMainThread());

  return mTopInnerWindow ?
    mTopInnerWindow->mNumOfIndexedDBDatabases > 0 :
    mNumOfIndexedDBDatabases > 0;
}

void
nsPIDOMWindowInner::UpdateWebSocketCount(int32_t aDelta)
{
  MOZ_ASSERT(NS_IsMainThread());

  if (aDelta == 0) {
    return;
  }

  if (mTopInnerWindow && !IsTopInnerWindow()) {
    mTopInnerWindow->UpdateWebSocketCount(aDelta);
  }

  MOZ_DIAGNOSTIC_ASSERT(
    aDelta > 0 || ((aDelta + mNumOfOpenWebSockets) < mNumOfOpenWebSockets));

  mNumOfOpenWebSockets += aDelta;
}

bool
nsPIDOMWindowInner::HasOpenWebSockets() const
{
  MOZ_ASSERT(NS_IsMainThread());

  return mNumOfOpenWebSockets ||
         (mTopInnerWindow && mTopInnerWindow->mNumOfOpenWebSockets);
}

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

nsresult
nsPIDOMWindowInner::SetAudioCapture(bool aCapture)
{
  mAudioCaptured = aCapture;

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

  return NS_OK;
}

// nsISpeechSynthesisGetter

#ifdef MOZ_WEBSPEECH
SpeechSynthesis*
nsGlobalWindowInner::GetSpeechSynthesis(ErrorResult& aError)
{
  if (!mSpeechSynthesis) {
    mSpeechSynthesis = new SpeechSynthesis(this);
  }

  return mSpeechSynthesis;
}

bool
nsGlobalWindowInner::HasActiveSpeechSynthesis()
{
  if (mSpeechSynthesis) {
    return !mSpeechSynthesis->HasEmptyQueue();
  }

  return false;
}

#endif

already_AddRefed<nsPIDOMWindowOuter>
nsGlobalWindowInner::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*
nsGlobalWindowInner::GetScriptableParent()
{
  FORWARD_TO_OUTER(GetScriptableParent, (), nullptr);
}

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

/**
 * 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*
nsGlobalWindowInner::GetScriptableTop()
{
  FORWARD_TO_OUTER(GetScriptableTop, (), nullptr);
}

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

BarProp*
nsGlobalWindowInner::GetMenubar(ErrorResult& aError)
{
  if (!mMenubar) {
    mMenubar = new MenubarProp(this);
  }

  return mMenubar;
}

BarProp*
nsGlobalWindowInner::GetToolbar(ErrorResult& aError)
{
  if (!mToolbar) {
    mToolbar = new ToolbarProp(this);
  }

  return mToolbar;
}

BarProp*
nsGlobalWindowInner::GetLocationbar(ErrorResult& aError)
{
  if (!mLocationbar) {
    mLocationbar = new LocationbarProp(this);
  }
  return mLocationbar;
}

BarProp*
nsGlobalWindowInner::GetPersonalbar(ErrorResult& aError)
{
  if (!mPersonalbar) {
    mPersonalbar = new PersonalbarProp(this);
  }
  return mPersonalbar;
}

BarProp*
nsGlobalWindowInner::GetStatusbar(ErrorResult& aError)
{
  if (!mStatusbar) {
    mStatusbar = new StatusbarProp(this);
  }
  return mStatusbar;
}

BarProp*
nsGlobalWindowInner::GetScrollbars(ErrorResult& aError)
{
  if (!mScrollbars) {
    mScrollbars = new ScrollbarsProp(this);
  }

  return mScrollbars;
}

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

nsDOMWindowList*
nsGlobalWindowInner::GetFrames()
{
  FORWARD_TO_OUTER(GetFrames, (), nullptr);
}

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

namespace {

struct InterfaceShimEntry {
  const char *geckoName;
  const char *domName;
};

} // anonymous namespace

// We add shims from Components.interfaces.nsIDOMFoo to window.Foo for each
// interface that has interface constants that sites might be getting off
// of Ci.
const InterfaceShimEntry kInterfaceShimMap[] =
{ { "nsIXMLHttpRequest", "XMLHttpRequest" },
  { "nsIDOMDOMException", "DOMException" },
  { "nsIDOMNode", "Node" },
  { "nsIDOMCSSRule", "CSSRule" },
  { "nsIDOMEvent", "Event" },
  { "nsIDOMNSEvent", "Event" },
  { "nsIDOMKeyEvent", "KeyEvent" },
  { "nsIDOMMouseEvent", "MouseEvent" },
  { "nsIDOMMouseScrollEvent", "MouseScrollEvent" },
  { "nsIDOMMutationEvent", "MutationEvent" },
  { "nsIDOMUIEvent", "UIEvent" },
  { "nsIDOMHTMLMediaElement", "HTMLMediaElement" },
  { "nsIDOMRange", "Range" },
  { "nsIDOMSVGLength", "SVGLength" },
  // Think about whether Ci.nsINodeFilter can just go away for websites!
  { "nsIDOMNodeFilter", "NodeFilter" },
  { "nsIDOMXPathResult", "XPathResult" } };

bool
nsGlobalWindowInner::ResolveComponentsShim(
  JSContext *aCx,
  JS::Handle<JSObject*> aGlobal,
  JS::MutableHandle<JS::PropertyDescriptor> aDesc)
{
  // Keep track of how often this happens.
  Telemetry::Accumulate(Telemetry::COMPONENTS_SHIM_ACCESSED_BY_CONTENT, true);

  // Warn once.
  nsCOMPtr<nsIDocument> doc = GetExtantDoc();
  if (doc) {
    doc->WarnOnceAbout(nsIDocument::eComponents, /* asError = */ true);
  }

  // Create a fake Components object.
  AssertSameCompartment(aCx, aGlobal);
  JS::Rooted<JSObject*> components(aCx, JS_NewPlainObject(aCx));
  if (NS_WARN_IF(!components)) {
    return false;
  }

  // Create a fake interfaces object.
  JS::Rooted<JSObject*> interfaces(aCx, JS_NewPlainObject(aCx));
  if (NS_WARN_IF(!interfaces)) {
    return false;
  }
  bool ok =
    JS_DefineProperty(aCx, components, "interfaces", interfaces,
                      JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
  if (NS_WARN_IF(!ok)) {
    return false;
  }

  // Define a bunch of shims from the Ci.nsIDOMFoo to window.Foo for DOM
  // interfaces with constants.
  for (uint32_t i = 0; i < ArrayLength(kInterfaceShimMap); ++i) {

    // Grab the names from the table.
    const char *geckoName = kInterfaceShimMap[i].geckoName;
    const char *domName = kInterfaceShimMap[i].domName;

    // Look up the appopriate interface object on the global.
    JS::Rooted<JS::Value> v(aCx, JS::UndefinedValue());
    ok = JS_GetProperty(aCx, aGlobal, domName, &v);
    if (NS_WARN_IF(!ok)) {
      return false;
    }
    if (!v.isObject()) {
      NS_WARNING("Unable to find interface object on global");
      continue;
    }

    // Define the shim on the interfaces object.
    ok = JS_DefineProperty(aCx, interfaces, geckoName, v,
                           JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
    if (NS_WARN_IF(!ok)) {
      return false;
    }
  }

  FillPropertyDescriptor(aDesc, aGlobal, JS::ObjectValue(*components), false);

  return true;
}

#ifdef RELEASE_OR_BETA
#define USE_CONTROLLERS_SHIM
#endif

#ifdef USE_CONTROLLERS_SHIM
static const JSClass ControllersShimClass = {
    "Controllers", 0
};
static const JSClass XULControllersShimClass = {
    "XULControllers", 0
};
#endif

bool
nsGlobalWindowInner::DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObj,
                               JS::Handle<jsid> aId,
                               JS::MutableHandle<JS::PropertyDescriptor> aDesc)
{
  // 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;
  }

  // We support a cut-down Components.interfaces in case websites are
  // using Components.interfaces.nsIFoo.CONSTANT_NAME for the ones
  // that have constants.
  static bool watchingComponentsPref = false;
  static bool useComponentsShim = false;
  if (!watchingComponentsPref) {
    watchingComponentsPref = true;
    Preferences::AddBoolVarCache(&useComponentsShim, "dom.use_components_shim",
                                 true);
  }
  if (useComponentsShim &&
      aId == XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS)) {
    return ResolveComponentsShim(aCx, aObj, aDesc);
  }

  // We also support a "window.controllers" thing; apparently some
  // sites use it for browser-sniffing.  See bug 1010577.
#ifdef USE_CONTROLLERS_SHIM
  // Note: We use |aObj| rather than |this| to get the principal here, because
  // this is called during Window setup when the Document isn't necessarily
  // hooked up yet.
  if ((aId == XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_CONTROLLERS) ||
       aId == XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_CONTROLLERS_CLASS)) &&
      !xpc::IsXrayWrapper(aObj) &&
      !nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(aObj)))
  {
    if (GetExtantDoc()) {
      GetExtantDoc()->WarnOnceAbout(nsIDocument::eWindow_Cc_ontrollers);
    }
    const JSClass* clazz;
    if (aId == XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_CONTROLLERS)) {
      clazz = &XULControllersShimClass;
    } else {
      clazz = &ControllersShimClass;
    }
    MOZ_ASSERT(JS_IsGlobalObject(aObj));
    JS::Rooted<JSObject*> shim(aCx, JS_NewObject(aCx, clazz));
    if (NS_WARN_IF(!shim)) {
      return false;
    }
    FillPropertyDescriptor(aDesc, aObj, JS::ObjectValue(*shim),
                           /* readOnly = */ false);
    return true;
  }
#endif

  return true;
}

/* static */
bool
nsGlobalWindowInner::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 == XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS)) {
    return true;
  }

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

  return WebIDLGlobalNameHash::MayResolve(aId);
}

void
nsGlobalWindowInner::GetOwnPropertyNames(JSContext* aCx, JS::AutoIdVector& aNames,
                                         bool aEnumerableOnly, ErrorResult& aRv)
{
  if (aEnumerableOnly) {
    // The names we would return from here get defined on the window via one of
    // two codepaths.  The ones coming from the WebIDLGlobalNameHash will end up
    // in the DefineConstructor function in BindingUtils, which always defines
    // things as non-enumerable.  The ones coming from the script namespace
    // manager get defined by our resolve hook using FillPropertyDescriptor with
    // 0 for the property attributes, so non-enumerable as well.
    //
    // So in the aEnumerableOnly case we have nothing to do.
    return;
  }

  // "Components" is marked as enumerable but only resolved on demand :-/.
  //aNames.AppendElement(NS_LITERAL_STRING("Components"));

  JS::Rooted<JSObject*> wrapper(aCx, GetWrapper());

  // There are actually two ways we can get called here: For normal
  // enumeration or for Xray enumeration.  In the latter case, we want to
  // return all possible WebIDL names, because we don't really support
  // deleting these names off our Xray; trying to resolve them will just make
  // them come back.  In the former case, we want to avoid returning deleted
  // names.  But the JS engine already knows about the non-deleted
  // already-resolved names, so we can just return the so-far-unresolved ones.
  //
  // We can tell which case we're in by whether aCx is in our wrapper's
  // compartment.  If not, we're in the Xray case.
  WebIDLGlobalNameHash::NameType nameType =
    js::IsObjectInContextCompartment(wrapper, aCx) ?
      WebIDLGlobalNameHash::UnresolvedNamesOnly :
      WebIDLGlobalNameHash::AllNames;
  if (!WebIDLGlobalNameHash::GetNames(aCx, wrapper, nameType, aNames)) {
    aRv.NoteJSContextException(aCx);
  }
}

/* static */ bool
nsGlobalWindowInner::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
nsGlobalWindowInner::OfflineCacheAllowedForContext(JSContext* aCx, JSObject* aObj)
{
  return IsSecureContextOrObjectIsFromSecureContext(aCx, aObj) ||
         Preferences::GetBool("browser.cache.offline.insecure.enable");
}

/* static */ bool
nsGlobalWindowInner::IsRequestIdleCallbackEnabled(JSContext* aCx, JSObject* aObj)
{
  // The requestIdleCallback should always be enabled for system code.
  return nsContentUtils::RequestIdleCallbackEnabled() ||
         nsContentUtils::IsSystemCaller(aCx);
}

/* static */ bool
nsGlobalWindowInner::RegisterProtocolHandlerAllowedForContext(JSContext* aCx, JSObject* aObj)
{
  return IsSecureContextOrObjectIsFromSecureContext(aCx, aObj) ||
         Preferences::GetBool("dom.registerProtocolHandler.insecure.enabled");
}

/* static */ bool
nsGlobalWindowInner::DeviceSensorsEnabled(JSContext* aCx, JSObject* aObj)
{
  return Preferences::GetBool("device.sensors.enabled");
}

nsIDOMOfflineResourceList*
nsGlobalWindowInner::GetApplicationCache(ErrorResult& aError)
{
  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(),
                                   this);

    applicationCache->Init();

    mApplicationCache = applicationCache;
  }

  return mApplicationCache;
}

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

Crypto*
nsGlobalWindowInner::GetCrypto(ErrorResult& aError)
{
  if (!mCrypto) {
    mCrypto = new Crypto(this);
  }
  return mCrypto;
}

mozilla::dom::U2F*
nsGlobalWindowInner::GetU2f(ErrorResult& aError)
{
  if (!mU2F) {
    RefPtr<U2F> u2f = new U2F(this);
    u2f->Init(aError);
    if (NS_WARN_IF(aError.Failed())) {
      return nullptr;
    }

    mU2F = u2f;
  }
  return mU2F;
}

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

nsresult
nsGlobalWindowInner::GetControllers(nsIControllers** aResult)
{
  ErrorResult rv;
  nsCOMPtr<nsIControllers> controllers = GetControllers(rv);
  controllers.forget(aResult);

  return rv.StealNSResult();
}

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

void
nsGlobalWindowInner::GetOpener(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval,
                               ErrorResult& aError)
{
  nsCOMPtr<nsPIDOMWindowOuter> opener = GetOpenerWindow(aError);
  if (aError.Failed() || !opener) {
    aRetval.setNull();
    return;
  }

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

void
nsGlobalWindowInner::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;
  }

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

  SetOpenerWindow(outer, false);
}

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

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

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

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

int32_t
nsGlobalWindowInner::GetInnerWidth(CallerType aCallerType, ErrorResult& aError)
{
  // We ignore aCallerType; we only have that argument because some other things
  // called by GetReplaceableWindowCoord need it.  If this ever changes, fix
  //   nsresult nsGlobalWindowInner::GetInnerWidth(int32_t* aInnerWidth)
  // to actually take a useful CallerType and pass it in here.
  FORWARD_TO_OUTER_OR_THROW(GetInnerWidthOuter, (aError), aError, 0);
}

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

nsresult
nsGlobalWindowInner::GetInnerWidth(int32_t* aInnerWidth)
{
  ErrorResult rv;
  // Callee doesn't care about the caller type, but play it safe.
  *aInnerWidth = GetInnerWidth(CallerType::NonSystem, rv);

  return rv.StealNSResult();
}

void
nsGlobalWindowInner::SetInnerWidth(int32_t aInnerWidth, CallerType aCallerType,
                                   ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetInnerWidthOuter,
                            (aInnerWidth, aCallerType, aError), aError, );
}

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

int32_t
nsGlobalWindowInner::GetInnerHeight(CallerType aCallerType, ErrorResult& aError)
{
  // We ignore aCallerType; we only have that argument because some other things
  // called by GetReplaceableWindowCoord need it.  If this ever changes, fix
  //   nsresult nsGlobalWindowInner::GetInnerHeight(int32_t* aInnerWidth)
  // to actually take a useful CallerType and pass it in here.
  FORWARD_TO_OUTER_OR_THROW(GetInnerHeightOuter, (aError), aError, 0);
}

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

nsresult
nsGlobalWindowInner::GetInnerHeight(int32_t* aInnerHeight)
{
  ErrorResult rv;
  // Callee doesn't care about the caller type, but play it safe.
  *aInnerHeight = GetInnerHeight(CallerType::NonSystem, rv);

  return rv.StealNSResult();
}

void
nsGlobalWindowInner::SetInnerHeight(int32_t aInnerHeight,
                                    CallerType aCallerType,
                                    ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetInnerHeightOuter,
                            (aInnerHeight, aCallerType, aError), aError, );
}

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

int32_t
nsGlobalWindowInner::GetOuterWidth(CallerType aCallerType, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetOuterWidthOuter, (aCallerType, aError),
                            aError, 0);
}

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

int32_t
nsGlobalWindowInner::GetOuterHeight(CallerType aCallerType, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetOuterHeightOuter, (aCallerType, aError),
                            aError, 0);
}

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

void
nsGlobalWindowInner::SetOuterWidth(int32_t aOuterWidth,
                                   CallerType aCallerType,
                                   ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetOuterWidthOuter,
                            (aOuterWidth, aCallerType, aError), aError, );
}

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

void
nsGlobalWindowInner::SetOuterHeight(int32_t aOuterHeight,
                                    CallerType aCallerType,
                                    ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetOuterHeightOuter,
                            (aOuterHeight, aCallerType, aError), aError, );
}

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

int32_t
nsGlobalWindowInner::GetScreenX(CallerType aCallerType, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetScreenXOuter, (aCallerType, aError), aError, 0);
}

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

float
nsGlobalWindowInner::GetMozInnerScreenX(CallerType aCallerType, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenXOuter, (aCallerType), aError, 0);
}

float
nsGlobalWindowInner::GetMozInnerScreenY(CallerType aCallerType, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenYOuter, (aCallerType), aError, 0);
}

double
nsGlobalWindowInner::GetDevicePixelRatio(CallerType aCallerType, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetDevicePixelRatioOuter, (aCallerType), aError, 0.0);
}

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

int32_t
nsGlobalWindowInner::RequestAnimationFrame(FrameRequestCallback& aCallback,
                                           ErrorResult& aError)
{
  if (!mDoc) {
    return 0;
  }

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

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

void
nsGlobalWindowInner::CancelAnimationFrame(int32_t aHandle, ErrorResult& aError)
{
  if (!mDoc) {
    return;
  }

  mDoc->CancelFrameRequestCallback(aHandle);
}

already_AddRefed<MediaQueryList>
nsGlobalWindowInner::MatchMedia(const nsAString& aMediaQueryList,
                                CallerType aCallerType,
                                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, aCallerType), aError, nullptr);
}

void
nsGlobalWindowInner::SetScreenX(int32_t aScreenX,
                                CallerType aCallerType,
                                ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetScreenXOuter,
                            (aScreenX, aCallerType, aError), aError, );
}

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

int32_t
nsGlobalWindowInner::GetScreenY(CallerType aCallerType, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetScreenYOuter, (aCallerType, aError), aError, 0);
}

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

void
nsGlobalWindowInner::SetScreenY(int32_t aScreenY,
                                CallerType aCallerType,
                                ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetScreenYOuter,
                            (aScreenY, aCallerType, aError), aError, );
}

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

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

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

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

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

double
nsGlobalWindowInner::GetScrollX(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetScrollXOuter, (), aError, 0);
}

double
nsGlobalWindowInner::GetScrollY(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetScrollYOuter, (), aError, 0);
}

uint32_t
nsGlobalWindowInner::Length()
{
  FORWARD_TO_OUTER(Length, (), 0);
}

already_AddRefed<nsPIDOMWindowOuter>
nsGlobalWindowInner::GetTop(mozilla::ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetTopOuter, (), aError, nullptr);
}

nsPIDOMWindowOuter*
nsGlobalWindowInner::GetChildWindow(const nsAString& aName)
{
  if (GetOuterWindowInternal()) {
    return GetOuterWindowInternal()->GetChildWindow(aName);
  }
  return nullptr;
}

void
nsGlobalWindowInner::RefreshRealmPrincipal()
{
  JS::SetRealmPrincipals(js::GetNonCCWObjectRealm(GetWrapperPreserveColor()),
                         nsJSPrincipals::get(mDoc->NodePrincipal()));
}

already_AddRefed<nsIWidget>
nsGlobalWindowInner::GetMainWidget()
{
  FORWARD_TO_OUTER(GetMainWidget, (), nullptr);
}

nsIWidget*
nsGlobalWindowInner::GetNearestWidget() const
{
  if (GetOuterWindowInternal()) {
    return GetOuterWindowInternal()->GetNearestWidget();
  }
  return nullptr;
}

void
nsGlobalWindowInner::SetFullScreen(bool aFullScreen, mozilla::ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetFullScreenOuter, (aFullScreen, aError), aError, /* void */);
}

bool
nsGlobalWindowInner::GetFullScreen(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetFullScreenOuter, (), aError, false);
}

bool
nsGlobalWindowInner::GetFullScreen()
{
  ErrorResult dummy;
  bool fullscreen = GetFullScreen(dummy);
  dummy.SuppressException();
  return fullscreen;
}

void
nsGlobalWindowInner::Dump(const nsAString& aStr)
{
  if (!DOMPrefs::DumpEnabled()) {
    return;
  }

  char *cstr = ToNewUTF8String(aStr);

#if defined(XP_MACOSX)
  // have to convert \r to \n so that printing to the console works
  char *c = cstr, *cEnd = cstr + strlen(cstr);
  while (c < cEnd) {
    if (*c == '\r')
      *c = '\n';
    c++;
  }
#endif

  if (cstr) {
    MOZ_LOG(nsContentUtils::DOMDumpLog(), LogLevel::Debug, ("[Window.Dump] %s", cstr));
#ifdef XP_WIN
    PrintToDebugger(cstr);
#endif
#ifdef ANDROID
    __android_log_write(ANDROID_LOG_INFO, "GeckoDump", cstr);
#endif
    FILE *fp = gDumpFile ? gDumpFile : stdout;
    fputs(cstr, fp);
    fflush(fp);
    free(cstr);
  }
}

void
nsGlobalWindowInner::Alert(nsIPrincipal& aSubjectPrincipal,
                           ErrorResult& aError)
{
  Alert(EmptyString(), aSubjectPrincipal, aError);
}

void
nsGlobalWindowInner::Alert(const nsAString& aMessage,
                           nsIPrincipal& aSubjectPrincipal,
                           ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(AlertOuter, (aMessage, aSubjectPrincipal, aError),
                            aError, );
}

bool
nsGlobalWindowInner::Confirm(const nsAString& aMessage,
                             nsIPrincipal& aSubjectPrincipal,
                             ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(ConfirmOuter, (aMessage, aSubjectPrincipal, aError),
                            aError, false);
}

already_AddRefed<Promise>
nsGlobalWindowInner::Fetch(const RequestOrUSVString& aInput,
                           const RequestInit& aInit,
                           CallerType aCallerType, ErrorResult& aRv)
{
  return FetchRequest(this, aInput, aInit, aCallerType, aRv);
}

void
nsGlobalWindowInner::Prompt(const nsAString& aMessage, const nsAString& aInitial,
                            nsAString& aReturn,
                            nsIPrincipal& aSubjectPrincipal,
                            ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(PromptOuter,
                            (aMessage, aInitial, aReturn, aSubjectPrincipal,
                             aError),
                            aError, );
}

void
nsGlobalWindowInner::Focus(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(FocusOuter, (aError), aError, );
}

nsresult
nsGlobalWindowInner::Focus()
{
  ErrorResult rv;
  Focus(rv);

  return rv.StealNSResult();
}

void
nsGlobalWindowInner::Blur(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(BlurOuter, (), aError, );
}

void
nsGlobalWindowInner::Back(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(BackOuter, (aError), aError, );
}

void
nsGlobalWindowInner::Forward(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(ForwardOuter, (aError), aError, );
}

void
nsGlobalWindowInner::Home(nsIPrincipal& aSubjectPrincipal, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(HomeOuter, (aSubjectPrincipal, aError), aError, );
}

void
nsGlobalWindowInner::Stop(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(StopOuter, (aError), aError, );
}

/* static */
bool
nsGlobalWindowInner::IsWindowPrintEnabled(JSContext*, JSObject*)
{
  static bool called = false;
  static bool printDisabled = false;
  if (!called) {
    called = true;
    Preferences::AddBoolVarCache(&printDisabled, "dom.disable_window_print");
  }
  return !printDisabled;
}

void
nsGlobalWindowInner::Print(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(PrintOuter, (aError), aError, );
}

void
nsGlobalWindowInner::MoveTo(int32_t aXPos, int32_t aYPos,
                            CallerType aCallerType, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(MoveToOuter,
                            (aXPos, aYPos, aCallerType, aError), aError, );
}

void
nsGlobalWindowInner::MoveBy(int32_t aXDif, int32_t aYDif,
                            CallerType aCallerType, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(MoveByOuter,
                            (aXDif, aYDif, aCallerType, aError), aError, );
}

void
nsGlobalWindowInner::ResizeTo(int32_t aWidth, int32_t aHeight,
                              CallerType aCallerType, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(ResizeToOuter,
                            (aWidth, aHeight, aCallerType, aError), aError, );
}

void
nsGlobalWindowInner::ResizeBy(int32_t aWidthDif, int32_t aHeightDif,
                              CallerType aCallerType, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(ResizeByOuter,
                            (aWidthDif, aHeightDif, aCallerType, aError),
                            aError, );
}

void
nsGlobalWindowInner::SizeToContent(CallerType aCallerType, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SizeToContentOuter, (aCallerType, aError),
                            aError, );
}

already_AddRefed<nsPIWindowRoot>
nsGlobalWindowInner::GetTopWindowRoot()
{
  nsGlobalWindowOuter* outer = GetOuterWindowInternal();
  if (!outer) {
    return nullptr;
  }
  return outer->GetTopWindowRoot();
}

void
nsGlobalWindowInner::Scroll(double aXScroll, double aYScroll)
{
  // Convert -Inf, Inf, and NaN to 0; otherwise, convert by C-style cast.
  auto scrollPos = CSSIntPoint::Truncate(mozilla::ToZeroIfNonfinite(aXScroll),
                                         mozilla::ToZeroIfNonfinite(aYScroll));
  ScrollTo(scrollPos, ScrollOptions());
}

void
nsGlobalWindowInner::ScrollTo(double aXScroll, double aYScroll)
{
  // Convert -Inf, Inf, and NaN to 0; otherwise, convert by C-style cast.
  auto scrollPos = CSSIntPoint::Truncate(mozilla::ToZeroIfNonfinite(aXScroll),
                                         mozilla::ToZeroIfNonfinite(aYScroll));
  ScrollTo(scrollPos, ScrollOptions());
}

void
nsGlobalWindowInner::ScrollTo(const ScrollToOptions& aOptions)
{
  // When scrolling to a non-zero offset, we need to determine whether that
  // position is within our scrollable range, so we need updated layout
  // information which requires a layout flush, otherwise all we need is to
  // flush frames to be able to access our scrollable frame here.
  FlushType flushType = ((aOptions.mLeft.WasPassed() &&
                          aOptions.mLeft.Value() > 0) ||
                         (aOptions.mTop.WasPassed() &&
                          aOptions.mTop.Value() > 0)) ?
                          FlushType::Layout :
                          FlushType::Frames;
  FlushPendingNotifications(flushType);
  nsIScrollableFrame *sf = GetScrollFrame();

  if (sf) {
    CSSIntPoint scrollPos = sf->GetScrollPositionCSSPixels();
    if (aOptions.mLeft.WasPassed()) {
      scrollPos.x = mozilla::ToZeroIfNonfinite(aOptions.mLeft.Value());
    }
    if (aOptions.mTop.WasPassed()) {
      scrollPos.y = mozilla::ToZeroIfNonfinite(aOptions.mTop.Value());
    }

    ScrollTo(scrollPos, aOptions);
  }
}

void
nsGlobalWindowInner::Scroll(const ScrollToOptions& aOptions)
{
  ScrollTo(aOptions);
}

void
nsGlobalWindowInner::ScrollTo(const CSSIntPoint& aScroll,
                              const ScrollOptions& aOptions)
{
  // When scrolling to a non-zero offset, we need to determine whether that
  // position is within our scrollable range, so we need updated layout
  // information which requires a layout flush, otherwise all we need is to
  // flush frames to be able to access our scrollable frame here.
  FlushType flushType = (aScroll.x || aScroll.y) ?
                          FlushType::Layout :
                          FlushType::Frames;
  FlushPendingNotifications(flushType);
  nsIScrollableFrame *sf = GetScrollFrame();

  if (sf) {
    // Here we calculate what the max pixel value is that we can
    // scroll to, we do this by dividing maxint with the pixel to
    // twips conversion factor, and subtracting 4, the 4 comes from
    // experimenting with this value, anything less makes the view
    // code not scroll correctly, I have no idea why. -- jst
    const int32_t maxpx = nsPresContext::AppUnitsToIntCSSPixels(0x7fffffff) - 4;

    CSSIntPoint scroll(aScroll);
    if (scroll.x > maxpx) {
      scroll.x = maxpx;
    }

    if (scroll.y > maxpx) {
      scroll.y = maxpx;
    }

    bool smoothScroll = sf->GetScrollbarStyles().IsSmoothScroll(aOptions.mBehavior);

    sf->ScrollToCSSPixels(scroll, smoothScroll
                            ? nsIScrollableFrame::SMOOTH_MSD
                            : nsIScrollableFrame::INSTANT);
  }
}

void
nsGlobalWindowInner::ScrollBy(double aXScrollDif, double aYScrollDif)
{
  FlushPendingNotifications(FlushType::Layout);
  nsIScrollableFrame *sf = GetScrollFrame();

  if (sf) {
    // Convert -Inf, Inf, and NaN to 0; otherwise, convert by C-style cast.
    auto scrollDif = CSSIntPoint::Truncate(mozilla::ToZeroIfNonfinite(aXScrollDif),
                                           mozilla::ToZeroIfNonfinite(aYScrollDif));
    // It seems like it would make more sense for ScrollBy to use
    // SMOOTH mode, but tests seem to depend on the synchronous behaviour.
    // Perhaps Web content does too.
    ScrollTo(sf->GetScrollPositionCSSPixels() + scrollDif, ScrollOptions());
  }
}

void
nsGlobalWindowInner::ScrollBy(const ScrollToOptions& aOptions)
{
  FlushPendingNotifications(FlushType::Layout);
  nsIScrollableFrame *sf = GetScrollFrame();

  if (sf) {
    CSSIntPoint scrollPos = sf->GetScrollPositionCSSPixels();
    if (aOptions.mLeft.WasPassed()) {
      scrollPos.x += mozilla::ToZeroIfNonfinite(aOptions.mLeft.Value());
    }
    if (aOptions.mTop.WasPassed()) {
      scrollPos.y += mozilla::ToZeroIfNonfinite(aOptions.mTop.Value());
    }

    ScrollTo(scrollPos, aOptions);
  }
}

void
nsGlobalWindowInner::ScrollByLines(int32_t numLines,
                                   const ScrollOptions& aOptions)
{
  FlushPendingNotifications(FlushType::Layout);
  nsIScrollableFrame *sf = GetScrollFrame();
  if (sf) {
    // It seems like it would make more sense for ScrollByLines to use
    // SMOOTH mode, but tests seem to depend on the synchronous behaviour.
    // Perhaps Web content does too.
    bool smoothScroll = sf->GetScrollbarStyles().IsSmoothScroll(aOptions.mBehavior);

    sf->ScrollBy(nsIntPoint(0, numLines), nsIScrollableFrame::LINES,
                 smoothScroll
                   ? nsIScrollableFrame::SMOOTH_MSD
                   : nsIScrollableFrame::INSTANT);
  }
}

void
nsGlobalWindowInner::ScrollByPages(int32_t numPages,
                                   const ScrollOptions& aOptions)
{
  FlushPendingNotifications(FlushType::Layout);
  nsIScrollableFrame *sf = GetScrollFrame();
  if (sf) {
    // It seems like it would make more sense for ScrollByPages to use
    // SMOOTH mode, but tests seem to depend on the synchronous behaviour.
    // Perhaps Web content does too.
    bool smoothScroll = sf->GetScrollbarStyles().IsSmoothScroll(aOptions.mBehavior);

    sf->ScrollBy(nsIntPoint(0, numPages), nsIScrollableFrame::PAGES,
                 smoothScroll
                   ? nsIScrollableFrame::SMOOTH_MSD
                   : nsIScrollableFrame::INSTANT);
  }
}

void
nsGlobalWindowInner::MozScrollSnap()
{
  FlushPendingNotifications(FlushType::Layout);
  nsIScrollableFrame *sf = GetScrollFrame();
  if (sf) {
    sf->ScrollSnap();
  }
}

void
nsGlobalWindowInner::ClearTimeout(int32_t aHandle)
{
  if (aHandle > 0) {
    mTimeoutManager->ClearTimeout(aHandle, Timeout::Reason::eTimeoutOrInterval);
  }
}

void
nsGlobalWindowInner::ClearInterval(int32_t aHandle)
{
  if (aHandle > 0) {
    mTimeoutManager->ClearTimeout(aHandle, Timeout::Reason::eTimeoutOrInterval);
  }
}

void
nsGlobalWindowInner::SetResizable(bool aResizable) const
{
  // nop
}

void
nsGlobalWindowInner::CaptureEvents()
{
  if (mDoc) {
    mDoc->WarnOnceAbout(nsIDocument::eUseOfCaptureEvents);
  }
}

void
nsGlobalWindowInner::ReleaseEvents()
{
  if (mDoc) {
    mDoc->WarnOnceAbout(nsIDocument::eUseOfReleaseEvents);
  }
}

already_AddRefed<nsPIDOMWindowOuter>
nsGlobalWindowInner::Open(const nsAString& aUrl, const nsAString& aName,
                          const nsAString& aOptions, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(OpenOuter, (aUrl, aName, aOptions, aError), aError,
                            nullptr);
}

already_AddRefed<nsPIDOMWindowOuter>
nsGlobalWindowInner::OpenDialog(JSContext* aCx, const nsAString& aUrl,
                                const nsAString& aName, const nsAString& aOptions,
                                const Sequence<JS::Value>& aExtraArgument,
                                ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(OpenDialogOuter,
                            (aCx, aUrl, aName, aOptions, aExtraArgument, aError),
                            aError, nullptr);
}

already_AddRefed<nsPIDOMWindowOuter>
nsGlobalWindowInner::GetFrames(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetFramesOuter, (), aError, nullptr);
}

nsGlobalWindowInner*
nsGlobalWindowInner::CallerInnerWindow()
{
  JSContext *cx = nsContentUtils::GetCurrentJSContext();
  NS_ENSURE_TRUE(cx, nullptr);
  nsIGlobalObject* global = GetIncumbentGlobal();
  NS_ENSURE_TRUE(global, nullptr);
  JS::Rooted<JSObject*> scope(cx, global->GetGlobalJSObject());
  NS_ENSURE_TRUE(scope, nullptr);

  // When Jetpack runs content scripts inside a sandbox, it uses
  // sandboxPrototype to make them appear as though they're running in the
  // scope of the page. So when a content script invokes postMessage, it expects
  // the |source| of the received message to be the window set as the
  // sandboxPrototype. This used to work incidentally for unrelated reasons, but
  // now we need to do some special handling to support it.
  if (xpc::IsSandbox(scope)) {
    JSAutoRealm ar(cx, scope);
    JS::Rooted<JSObject*> scopeProto(cx);
    bool ok = JS_GetPrototype(cx, scope, &scopeProto);
    NS_ENSURE_TRUE(ok, nullptr);
    if (scopeProto && xpc::IsSandboxPrototypeProxy(scopeProto) &&
        (scopeProto = js::CheckedUnwrap(scopeProto, /* stopAtWindowProxy = */ false)))
    {
      global = xpc::NativeGlobal(scopeProto);
      NS_ENSURE_TRUE(global, nullptr);
    }
  }

  // The calling window must be holding a reference, so we can return a weak
  // pointer.
  nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
  return nsGlobalWindowInner::Cast(win);
}

void
nsGlobalWindowInner::PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                                    const nsAString& aTargetOrigin,
                                    JS::Handle<JS::Value> aTransfer,
                                    nsIPrincipal& aSubjectPrincipal,
                                    ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(PostMessageMozOuter,
                            (aCx, aMessage, aTargetOrigin, aTransfer,
                             aSubjectPrincipal, aError),
                            aError, );
}

void
nsGlobalWindowInner::PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                                    const nsAString& aTargetOrigin,
                                    const Sequence<JSObject*>& aTransfer,
                                    nsIPrincipal& aSubjectPrincipal,
                                    ErrorResult& aRv)
{
  JS::Rooted<JS::Value> transferArray(aCx, JS::UndefinedValue());

  aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransfer,
                                                          &transferArray);
  if (NS_WARN_IF(aRv.Failed())) {
    return;
  }

  PostMessageMoz(aCx, aMessage, aTargetOrigin, transferArray,
                 aSubjectPrincipal, aRv);
}

void
nsGlobalWindowInner::Close(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(CloseOuter, (nsContentUtils::IsCallerChrome()), aError, );
}

nsresult
nsGlobalWindowInner::Close()
{
  FORWARD_TO_OUTER(Close, (), NS_ERROR_UNEXPECTED);
}

void
nsGlobalWindowInner::ReallyCloseWindow()
{
  FORWARD_TO_OUTER_VOID(ReallyCloseWindow, ());
}

bool
nsGlobalWindowInner::IsInModalState()
{
  FORWARD_TO_OUTER(IsInModalState, (), false);
}

// static
void
nsGlobalWindowInner::NotifyDOMWindowDestroyed(nsGlobalWindowInner* aWindow)
{
  nsCOMPtr<nsIObserverService> observerService =
    services::GetObserverService();
  if (observerService) {
    observerService->
      NotifyObservers(ToSupports(aWindow),
                      DOM_WINDOW_DESTROYED_TOPIC, nullptr);
  }
}

void
nsGlobalWindowInner::NotifyWindowIDDestroyed(const char* aTopic)
{
  nsCOMPtr<nsIRunnable> runnable =
    new WindowDestroyedEvent(this, mWindowID, aTopic);
  Dispatch(TaskCategory::Other, runnable.forget());
}

// static
void
nsGlobalWindowInner::NotifyDOMWindowFrozen(nsGlobalWindowInner* aWindow)
{
  if (aWindow) {
    nsCOMPtr<nsIObserverService> observerService =
      services::GetObserverService();
    if (observerService) {
      observerService->
        NotifyObservers(ToSupports(aWindow),
                        DOM_WINDOW_FROZEN_TOPIC, nullptr);
    }
  }
}

// static
void
nsGlobalWindowInner::NotifyDOMWindowThawed(nsGlobalWindowInner* aWindow)
{
  if (aWindow) {
    nsCOMPtr<nsIObserverService> observerService =
      services::GetObserverService();
    if (observerService) {
      observerService->
        NotifyObservers(ToSupports(aWindow),
                        DOM_WINDOW_THAWED_TOPIC, nullptr);
    }
  }
}

JSObject*
nsGlobalWindowInner::GetCachedXBLPrototypeHandler(nsXBLPrototypeHandler* aKey)
{
  JS::Rooted<JSObject*> handler(RootingCx());
  if (mCachedXBLPrototypeHandlers) {
    mCachedXBLPrototypeHandlers->Get(aKey, handler.address());
  }
  return handler;
}

void
nsGlobalWindowInner::CacheXBLPrototypeHandler(nsXBLPrototypeHandler* aKey,
                                              JS::Handle<JSObject*> aHandler)
{
  if (!mCachedXBLPrototypeHandlers) {
    mCachedXBLPrototypeHandlers = MakeUnique<XBLPrototypeHandlerTable>();
    PreserveWrapper(ToSupports(this));
  }

  mCachedXBLPrototypeHandlers->Put(aKey, aHandler);
}

Element*
nsGlobalWindowInner::GetFrameElement(nsIPrincipal& aSubjectPrincipal,
                                     ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetFrameElementOuter, (aSubjectPrincipal), aError,
                            nullptr);
}

Element*
nsGlobalWindowInner::GetRealFrameElement(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetRealFrameElementOuter, (), aError, nullptr);
}

/**
 * nsIGlobalWindow::GetFrameElement (when called from C++) is just a wrapper
 * around GetRealFrameElement.
 */
Element*
nsGlobalWindowInner::GetFrameElement()
{
  return GetRealFrameElement(IgnoreErrors());
}

/* static */ bool
nsGlobalWindowInner::TokenizeDialogOptions(nsAString& aToken,
                                           nsAString::const_iterator& aIter,
                                           nsAString::const_iterator aEnd)
{
  while (aIter != aEnd && nsCRT::IsAsciiSpace(*aIter)) {
    ++aIter;
  }

  if (aIter == aEnd) {
    return false;
  }

  if (*aIter == ';' || *aIter == ':' || *aIter == '=') {
    aToken.Assign(*aIter);
    ++aIter;
    return true;
  }

  nsAString::const_iterator start = aIter;

  // Skip characters until we find whitespace, ';', ':', or '='
  while (aIter != aEnd && !nsCRT::IsAsciiSpace(*aIter) &&
         *aIter != ';' &&
         *aIter != ':' &&
         *aIter != '=') {
    ++aIter;
  }

  aToken.Assign(Substring(start, aIter));
  return true;
}

// Helper for converting window.showModalDialog() options (list of ';'
// separated name (:|=) value pairs) to a format that's parsable by
// our normal window opening code.

/* static */
void
nsGlobalWindowInner::ConvertDialogOptions(const nsAString& aOptions,
                                          nsAString& aResult)
{
  nsAString::const_iterator end;
  aOptions.EndReading(end);

  nsAString::const_iterator iter;
  aOptions.BeginReading(iter);

  nsAutoString token;
  nsAutoString name;
  nsAutoString value;

  while (true) {
    if (!TokenizeDialogOptions(name, iter, end)) {
      break;
    }

    // Invalid name.
    if (name.EqualsLiteral("=") ||
        name.EqualsLiteral(":") ||
        name.EqualsLiteral(";")) {
      break;
    }

    if (!TokenizeDialogOptions(token, iter, end)) {
      break;
    }

    if (!token.EqualsLiteral(":") && !token.EqualsLiteral("=")) {
      continue;
    }

    // We found name followed by ':' or '='. Look for a value.
    if (!TokenizeDialogOptions(value, iter, end)) {
      break;
    }

    if (name.LowerCaseEqualsLiteral("center")) {
      if (value.LowerCaseEqualsLiteral("on")  ||
          value.LowerCaseEqualsLiteral("yes") ||
          value.LowerCaseEqualsLiteral("1")) {
        aResult.AppendLiteral(",centerscreen=1");
      }
    } else if (name.LowerCaseEqualsLiteral("dialogwidth")) {
      if (!value.IsEmpty()) {
        aResult.AppendLiteral(",width=");
        aResult.Append(value);
      }
    } else if (name.LowerCaseEqualsLiteral("dialogheight")) {
      if (!value.IsEmpty()) {
        aResult.AppendLiteral(",height=");
        aResult.Append(value);
      }
    } else if (name.LowerCaseEqualsLiteral("dialogtop")) {
      if (!value.IsEmpty()) {
        aResult.AppendLiteral(",top=");
        aResult.Append(value);
      }
    } else if (name.LowerCaseEqualsLiteral("dialogleft")) {
      if (!value.IsEmpty()) {
        aResult.AppendLiteral(",left=");
        aResult.Append(value);
      }
    } else if (name.LowerCaseEqualsLiteral("resizable")) {
      if (value.LowerCaseEqualsLiteral("on")  ||
          value.LowerCaseEqualsLiteral("yes") ||
          value.LowerCaseEqualsLiteral("1")) {
        aResult.AppendLiteral(",resizable=1");
      }
    } else if (name.LowerCaseEqualsLiteral("scroll")) {
      if (value.LowerCaseEqualsLiteral("off")  ||
          value.LowerCaseEqualsLiteral("no") ||
          value.LowerCaseEqualsLiteral("0")) {
        aResult.AppendLiteral(",scrollbars=0");
      }
    }

    if (iter == end ||
        !TokenizeDialogOptions(token, iter, end) ||
        !token.EqualsLiteral(";")) {
      break;
    }
  }
}

void
nsGlobalWindowInner::UpdateCommands(const nsAString& anAction,
                                    Selection* aSel,
                                    int16_t aReason)
{
  if (GetOuterWindowInternal()) {
    GetOuterWindowInternal()->UpdateCommands(anAction, aSel, aReason);
  }
}

Selection*
nsGlobalWindowInner::GetSelection(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetSelectionOuter, (), aError, nullptr);
}

bool
nsGlobalWindowInner::Find(const nsAString& aString, bool aCaseSensitive,
                          bool aBackwards, bool aWrapAround, bool aWholeWord,
                          bool aSearchInFrames, bool aShowDialog,
                          ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(FindOuter,
                            (aString, aCaseSensitive, aBackwards, aWrapAround,
                             aWholeWord, aSearchInFrames, aShowDialog, aError),
                            aError, false);
}

void
nsGlobalWindowInner::GetOrigin(nsAString& aOrigin)
{
  nsContentUtils::GetUTFOrigin(GetPrincipal(), aOrigin);
}

void
nsGlobalWindowInner::Atob(const nsAString& aAsciiBase64String,
                          nsAString& aBinaryData, ErrorResult& aError)
{
  aError = nsContentUtils::Atob(aAsciiBase64String, aBinaryData);
}

void
nsGlobalWindowInner::Btoa(const nsAString& aBinaryData,
                          nsAString& aAsciiBase64String, ErrorResult& aError)
{
  aError = nsContentUtils::Btoa(aBinaryData, aAsciiBase64String);
}

//*****************************************************************************
// EventTarget
//*****************************************************************************

nsPIDOMWindowOuter*
nsGlobalWindowInner::GetOwnerGlobalForBindings()
{
  return nsPIDOMWindowOuter::GetFromCurrentInner(this);
}

bool
nsGlobalWindowInner::DispatchEvent(Event& aEvent,
                                   CallerType aCallerType,
                                   ErrorResult& aRv)
{
  if (!IsCurrentInnerWindow()) {
    NS_WARNING("DispatchEvent called on non-current inner window, dropping. "
               "Please check the window in the caller instead.");
    aRv.Throw(NS_ERROR_FAILURE);
    return false;
  }

  if (!mDoc) {
    aRv.Throw(NS_ERROR_FAILURE);
    return false;
  }

  // Obtain a presentation shell
  RefPtr<nsPresContext> presContext = mDoc->GetPresContext();

  nsEventStatus status = nsEventStatus_eIgnore;
  nsresult rv = EventDispatcher::DispatchDOMEvent(ToSupports(this), nullptr,
                                                  &aEvent, presContext, &status);
  bool retval = !aEvent.DefaultPrevented(aCallerType);
  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
  }
  return retval;
}

bool
nsGlobalWindowInner::ComputeDefaultWantsUntrusted(ErrorResult& aRv)
{
  return !nsContentUtils::IsChromeDoc(mDoc);
}

EventListenerManager*
nsGlobalWindowInner::GetOrCreateListenerManager()
{
  if (!mListenerManager) {
    mListenerManager =
      new EventListenerManager(static_cast<EventTarget*>(this));
  }

  return mListenerManager;
}

EventListenerManager*
nsGlobalWindowInner::GetExistingListenerManager() const
{
  return mListenerManager;
}

//*****************************************************************************
// nsGlobalWindowInner::nsPIDOMWindow
//*****************************************************************************

nsPIDOMWindowOuter*
nsGlobalWindowInner::GetPrivateRoot()
{
  nsGlobalWindowOuter* outer = GetOuterWindowInternal();
  if (!outer) {
    NS_WARNING("No outer window available!");
    return nullptr;
  }
  return outer->GetPrivateRoot();
}

Location*
nsGlobalWindowInner::GetLocation()
{
  if (!mLocation) {
    mLocation = new dom::Location(this, GetDocShell());
  }

  return mLocation;
}

bool
nsGlobalWindowInner::IsTopLevelWindowActive()
{
  if (GetOuterWindowInternal()) {
    return GetOuterWindowInternal()->IsTopLevelWindowActive();
  }
  return false;
}

void
nsGlobalWindowInner::MaybeUpdateTouchState()
{
  if (mMayHaveTouchEventListener) {
    nsCOMPtr<nsIObserverService> observerService =
      services::GetObserverService();

    if (observerService) {
      observerService->NotifyObservers(static_cast<nsIDOMWindow*>(this),
                                       DOM_TOUCH_LISTENER_ADDED,
                                       nullptr);
    }
  }
}

void
nsGlobalWindowInner::EnableGamepadUpdates()
{
  if (mHasGamepad) {
    RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
    if (gamepadManager) {
      gamepadManager->AddListener(this);
    }
  }
}

void
nsGlobalWindowInner::DisableGamepadUpdates()
{
  if (mHasGamepad) {
    RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
    if (gamepadManager) {
      gamepadManager->RemoveListener(this);
    }
  }
}

void
nsGlobalWindowInner::EnableVRUpdates()
{
  if (mHasVREvents && !mVREventObserver) {
    mVREventObserver = new VREventObserver(this);
  }
}

void
nsGlobalWindowInner::DisableVRUpdates()
{
  if (mVREventObserver) {
    mVREventObserver->DisconnectFromOwner();
    mVREventObserver = nullptr;
  }
}

void
nsGlobalWindowInner::ResetVRTelemetry(bool aUpdate)
{
  if (mVREventObserver) {
    mVREventObserver->UpdateSpentTimeIn2DTelemetry(aUpdate);
  }
}

static bool ShouldShowFocusRingIfFocusedByMouse(nsIContent* aNode)
{
  if (!aNode) {
    return true;
  }
  return !nsContentUtils::ContentIsLink(aNode) &&
    !aNode->IsAnyOfHTMLElements(nsGkAtoms::video, nsGkAtoms::audio);
}

void
nsGlobalWindowInner::SetFocusedElement(Element* aElement,
                                       uint32_t aFocusMethod,
                                       bool aNeedsFocus)
{
  if (aElement && aElement->GetComposedDoc() != mDoc) {
    NS_WARNING("Trying to set focus to a node from a wrong document");
    return;
  }

  if (IsDying()) {
    NS_ASSERTION(!aElement, "Trying to focus cleaned up window!");
    aElement = nullptr;
    aNeedsFocus = false;
  }
  if (mFocusedElement != aElement) {
    UpdateCanvasFocus(false, aElement);
    mFocusedElement = aElement;
    mFocusMethod = aFocusMethod & FOCUSMETHOD_MASK;
    mShowFocusRingForContent = false;
  }

  if (mFocusedElement) {
    // if a node was focused by a keypress, turn on focus rings for the
    // window.
    if (mFocusMethod & nsIFocusManager::FLAG_BYKEY) {
      mFocusByKeyOccurred = true;
    } else if (
      // otherwise, we set mShowFocusRingForContent, as we don't want this to
      // be permanent for the window. On Windows, focus rings are only shown
      // when the FLAG_SHOWRING flag is used. On other platforms, focus rings
      // are only visible on some elements.
#ifndef XP_WIN
      !(mFocusMethod & nsIFocusManager::FLAG_BYMOUSE) ||
      ShouldShowFocusRingIfFocusedByMouse(aElement) ||
#endif
      aFocusMethod & nsIFocusManager::FLAG_SHOWRING) {
        mShowFocusRingForContent = true;
    }
  }

  if (aNeedsFocus)
    mNeedsFocus = aNeedsFocus;
}

uint32_t
nsGlobalWindowInner::GetFocusMethod()
{
  return mFocusMethod;
}

bool
nsGlobalWindowInner::ShouldShowFocusRing()
{
  if (mShowFocusRingForContent || mFocusByKeyOccurred) {
    return true;
  }

  nsCOMPtr<nsPIWindowRoot> root = GetTopWindowRoot();
  return root ? root->ShowFocusRings() : false;
}

bool
nsGlobalWindowInner::TakeFocus(bool aFocus, uint32_t aFocusMethod)
{
  if (IsDying()) {
    return false;
  }

  if (aFocus)
    mFocusMethod = aFocusMethod & FOCUSMETHOD_MASK;

  if (mHasFocus != aFocus) {
    mHasFocus = aFocus;
    UpdateCanvasFocus(true, mFocusedElement);
  }

  // if mNeedsFocus is true, then the document has not yet received a
  // document-level focus event. If there is a root content node, then return
  // true to tell the calling focus manager that a focus event is expected. If
  // there is no root content node, the document hasn't loaded enough yet, or
  // there isn't one and there is no point in firing a focus event.
  if (aFocus && mNeedsFocus && mDoc && mDoc->GetRootElement() != nullptr) {
    mNeedsFocus = false;
    return true;
  }

  mNeedsFocus = false;
  return false;
}

void
nsGlobalWindowInner::SetReadyForFocus()
{
  bool oldNeedsFocus = mNeedsFocus;
  mNeedsFocus = false;

  nsIFocusManager* fm = nsFocusManager::GetFocusManager();
  if (fm) {
    fm->WindowShown(GetOuterWindow(), oldNeedsFocus);
  }
}

void
nsGlobalWindowInner::PageHidden()
{
  // the window is being hidden, so tell the focus manager that the frame is
  // no longer valid. Use the persisted field to determine if the document
  // is being destroyed.

  nsIFocusManager* fm = nsFocusManager::GetFocusManager();
  if (fm) {
    fm->WindowHidden(GetOuterWindow());
  }

  mNeedsFocus = true;
}

class HashchangeCallback : public Runnable
{
public:
  HashchangeCallback(const nsAString& aOldURL,
                     const nsAString& aNewURL,
                     nsGlobalWindowInner* aWindow)
    : mozilla::Runnable("HashchangeCallback")
    , mWindow(aWindow)
  {
    MOZ_ASSERT(mWindow);
    mOldURL.Assign(aOldURL);
    mNewURL.Assign(aNewURL);
  }

  NS_IMETHOD Run() override
  {
    MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread.");
    return mWindow->FireHashchange(mOldURL, mNewURL);
  }

private:
  nsString mOldURL;
  nsString mNewURL;
  RefPtr<nsGlobalWindowInner> mWindow;
};

nsresult
nsGlobalWindowInner::DispatchAsyncHashchange(nsIURI *aOldURI, nsIURI *aNewURI)
{
  // Make sure that aOldURI and aNewURI are identical up to the '#', and that
  // their hashes are different.
  bool equal = false;
  NS_ENSURE_STATE(NS_SUCCEEDED(aOldURI->EqualsExceptRef(aNewURI, &equal)) && equal);
  nsAutoCString oldHash, newHash;
  bool oldHasHash, newHasHash;
  NS_ENSURE_STATE(NS_SUCCEEDED(aOldURI->GetRef(oldHash)) &&
                  NS_SUCCEEDED(aNewURI->GetRef(newHash)) &&
                  NS_SUCCEEDED(aOldURI->GetHasRef(&oldHasHash)) &&
                  NS_SUCCEEDED(aNewURI->GetHasRef(&newHasHash)) &&
                  (oldHasHash != newHasHash || !oldHash.Equals(newHash)));

  nsAutoCString oldSpec, newSpec;
  nsresult rv = aOldURI->GetSpec(oldSpec);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = aNewURI->GetSpec(newSpec);
  NS_ENSURE_SUCCESS(rv, rv);

  NS_ConvertUTF8toUTF16 oldWideSpec(oldSpec);
  NS_ConvertUTF8toUTF16 newWideSpec(newSpec);

  nsCOMPtr<nsIRunnable> callback =
    new HashchangeCallback(oldWideSpec, newWideSpec, this);
  return Dispatch(TaskCategory::Other, callback.forget());
}

nsresult
nsGlobalWindowInner::FireHashchange(const nsAString &aOldURL,
                                    const nsAString &aNewURL)
{
  // Don't do anything if the window is frozen.
  if (IsFrozen()) {
    return NS_OK;
  }

  // Get a presentation shell for use in creating the hashchange event.
  NS_ENSURE_STATE(IsCurrentInnerWindow());

  HashChangeEventInit init;
  init.mBubbles = true;
  init.mCancelable = false;
  init.mNewURL = aNewURL;
  init.mOldURL = aOldURL;

  RefPtr<HashChangeEvent> event =
    HashChangeEvent::Constructor(this, NS_LITERAL_STRING("hashchange"),
                                 init);

  event->SetTrusted(true);

  ErrorResult rv;
  DispatchEvent(*event, rv);
  return rv.StealNSResult();
}

nsresult
nsGlobalWindowInner::DispatchSyncPopState()
{
  NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
               "Must be safe to run script here.");

  nsresult rv = NS_OK;

  // Bail if the window is frozen.
  if (IsFrozen()) {
    return NS_OK;
  }

  // Get the document's pending state object -- it contains the data we're
  // going to send along with the popstate event.  The object is serialized
  // using structured clone.
  nsCOMPtr<nsIVariant> stateObj;
  rv = mDoc->GetStateObject(getter_AddRefs(stateObj));
  NS_ENSURE_SUCCESS(rv, rv);

  bool result = true;
  AutoJSAPI jsapi;
  result = jsapi.Init(this);
  NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);

  JSContext* cx = jsapi.cx();
  JS::Rooted<JS::Value> stateJSValue(cx, JS::NullValue());
  result = stateObj ? VariantToJsval(cx, stateObj, &stateJSValue) : true;
  NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);

  RootedDictionary<PopStateEventInit> init(cx);
  init.mBubbles = true;
  init.mCancelable = false;
  init.mState = stateJSValue;

  RefPtr<PopStateEvent> event =
    PopStateEvent::Constructor(this, NS_LITERAL_STRING("popstate"),
                               init);
  event->SetTrusted(true);
  event->SetTarget(this);

  ErrorResult err;
  DispatchEvent(*event, err);
  return err.StealNSResult();
}

// Find an nsICanvasFrame under aFrame.  Only search the principal
// child lists.  aFrame must be non-null.
static nsCanvasFrame*
FindCanvasFrame(nsIFrame* aFrame)
{
    nsCanvasFrame* canvasFrame = do_QueryFrame(aFrame);
    if (canvasFrame) {
        return canvasFrame;
    }

    for (nsIFrame* kid : aFrame->PrincipalChildList()) {
        canvasFrame = FindCanvasFrame(kid);
        if (canvasFrame) {
            return canvasFrame;
        }
    }

    return nullptr;
}

//-------------------------------------------------------
// Tells the HTMLFrame/CanvasFrame that is now has focus
void
nsGlobalWindowInner::UpdateCanvasFocus(bool aFocusChanged, nsIContent* aNewContent)
{
  // this is called from the inner window so use GetDocShell
  nsIDocShell* docShell = GetDocShell();
  if (!docShell)
    return;

  bool editable;
  docShell->GetEditable(&editable);
  if (editable)
    return;

  nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
  if (!presShell || !mDoc)
    return;

  Element *rootElement = mDoc->GetRootElement();
  if (rootElement) {
      if ((mHasFocus || aFocusChanged) &&
          (mFocusedElement == rootElement || aNewContent == rootElement)) {
          nsIFrame* frame = rootElement->GetPrimaryFrame();
          if (frame) {
              frame = frame->GetParent();
              nsCanvasFrame* canvasFrame = do_QueryFrame(frame);
              if (canvasFrame) {
                  canvasFrame->SetHasFocus(mHasFocus && rootElement == aNewContent);
              }
          }
      }
  } else {
      // Look for the frame the hard way
      nsIFrame* frame = presShell->GetRootFrame();
      if (frame) {
          nsCanvasFrame* canvasFrame = FindCanvasFrame(frame);
          if (canvasFrame) {
              canvasFrame->SetHasFocus(false);
          }
      }
  }
}

already_AddRefed<nsICSSDeclaration>
nsGlobalWindowInner::GetComputedStyle(Element& aElt, const nsAString& aPseudoElt,
                                      ErrorResult& aError)
{
  return GetComputedStyleHelper(aElt, aPseudoElt, false, aError);
}

already_AddRefed<nsICSSDeclaration>
nsGlobalWindowInner::GetDefaultComputedStyle(Element& aElt,
                                             const nsAString& aPseudoElt,
                                             ErrorResult& aError)
{
  return GetComputedStyleHelper(aElt, aPseudoElt, true, aError);
}

already_AddRefed<nsICSSDeclaration>
nsGlobalWindowInner::GetComputedStyleHelper(Element& aElt,
                                            const nsAString& aPseudoElt,
                                            bool aDefaultStylesOnly,
                                            ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetComputedStyleHelperOuter,
                            (aElt, aPseudoElt, aDefaultStylesOnly),
                            aError, nullptr);
}

Storage*
nsGlobalWindowInner::GetSessionStorage(ErrorResult& aError)
{
  nsIPrincipal *principal = GetPrincipal();
  nsIDocShell* docShell = GetDocShell();

  if (!principal || !docShell || !Storage::StoragePrefIsEnabled()) {
    return nullptr;
  }

  if (mSessionStorage) {
    MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
            ("nsGlobalWindowInner %p has %p sessionStorage", this, mSessionStorage.get()));
    bool canAccess = principal->Subsumes(mSessionStorage->Principal());
    NS_ASSERTION(canAccess,
                 "This window owned sessionStorage "
                 "that could not be accessed!");
    if (!canAccess) {
      mSessionStorage = nullptr;
    }
  }

  if (!mSessionStorage) {
    nsString documentURI;
    if (mDoc) {
      aError = mDoc->GetDocumentURI(documentURI);
      if (NS_WARN_IF(aError.Failed())) {
        return nullptr;
      }
    }

    // If the document has the sandboxed origin flag set
    // don't allow access to sessionStorage.
    if (!mDoc) {
      aError.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }

    if (mDoc->GetSandboxFlags() & SANDBOXED_ORIGIN) {
      aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
      return nullptr;
    }

    nsresult rv;

    nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(docShell, &rv);
    if (NS_FAILED(rv)) {
      aError.Throw(rv);
      return nullptr;
    }

    nsCOMPtr<nsIDOMStorage> storage;
    aError = storageManager->CreateStorage(this, principal, documentURI,
                                           IsPrivateBrowsing(),
                                           getter_AddRefs(storage));
    if (aError.Failed()) {
      return nullptr;
    }

    mSessionStorage = static_cast<Storage*>(storage.get());
    MOZ_ASSERT(mSessionStorage);

    MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
            ("nsGlobalWindowInner %p tried to get a new sessionStorage %p", this, mSessionStorage.get()));

    if (!mSessionStorage) {
      aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
      return nullptr;
    }
  }

  MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
          ("nsGlobalWindowInner %p returns %p sessionStorage", this, mSessionStorage.get()));

  return mSessionStorage;
}

Storage*
nsGlobalWindowInner::GetLocalStorage(ErrorResult& aError)
{
  if (!Storage::StoragePrefIsEnabled()) {
    return nullptr;
  }

  if (!mLocalStorage) {
    if (nsContentUtils::StorageAllowedForWindow(this) ==
          nsContentUtils::StorageAccess::eDeny) {
      aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
      return nullptr;
    }

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

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

    nsString documentURI;
    if (mDoc) {
      aError = mDoc->GetDocumentURI(documentURI);
      if (NS_WARN_IF(aError.Failed())) {
        return nullptr;
      }
    }

    nsCOMPtr<nsIDOMStorage> storage;
    aError = storageManager->CreateStorage(this, principal, documentURI,
                                           IsPrivateBrowsing(),
                                           getter_AddRefs(storage));
    if (aError.Failed()) {
      return nullptr;
    }

    mLocalStorage = static_cast<Storage*>(storage.get());
    MOZ_ASSERT(mLocalStorage);
  }

  return mLocalStorage;
}

IDBFactory*
nsGlobalWindowInner::GetIndexedDB(ErrorResult& aError)
{
  if (!mIndexedDB) {
    // This may keep mIndexedDB null without setting an error.
    aError = IDBFactory::CreateForWindow(this,
                                         getter_AddRefs(mIndexedDB));
  }

  return mIndexedDB;
}

void
nsGlobalWindowInner::AddPendingPromise(mozilla::dom::Promise* aPromise)
{
  mPendingPromises.AppendElement(aPromise);
}

void
nsGlobalWindowInner::RemovePendingPromise(mozilla::dom::Promise* aPromise)
{
  DebugOnly<bool> foundIt = mPendingPromises.RemoveElement(aPromise);
  MOZ_ASSERT(foundIt, "tried to remove a non-existent element from mPendingPromises");
}

//*****************************************************************************
// nsGlobalWindowInner::nsIInterfaceRequestor
//*****************************************************************************

NS_IMETHODIMP
nsGlobalWindowInner::GetInterface(const nsIID & aIID, void **aSink)
{
  nsGlobalWindowOuter* outer = GetOuterWindowInternal();
  NS_ENSURE_TRUE(outer, NS_ERROR_NOT_INITIALIZED);

  nsresult rv = outer->GetInterfaceInternal(aIID, aSink);
  if (rv == NS_ERROR_NO_INTERFACE) {
    return QueryInterface(aIID, aSink);
  }
  return rv;
}

void
nsGlobalWindowInner::GetInterface(JSContext* aCx, nsIJSID* aIID,
                                  JS::MutableHandle<JS::Value> aRetval,
                                  ErrorResult& aError)
{
  dom::GetInterface(aCx, this, aIID, aRetval, aError);
}

already_AddRefed<CacheStorage>
nsGlobalWindowInner::GetCaches(ErrorResult& aRv)
{
  if (!mCacheStorage) {
    bool forceTrustedOrigin =
      GetOuterWindow()->GetServiceWorkersTestingEnabled();

    nsContentUtils::StorageAccess access =
      nsContentUtils::StorageAllowedForWindow(this);

    // We don't block the cache API when being told to only allow storage for the
    // current session.
    bool storageBlocked = access <= nsContentUtils::StorageAccess::ePrivateBrowsing;

    mCacheStorage = CacheStorage::CreateOnMainThread(cache::DEFAULT_NAMESPACE,
                                                     this, GetPrincipal(),
                                                     storageBlocked,
                                                     forceTrustedOrigin, aRv);
  }

  RefPtr<CacheStorage> ref = mCacheStorage;
  return ref.forget();
}

void
nsGlobalWindowInner::FireOfflineStatusEventIfChanged()
{
  if (!IsCurrentInnerWindow())
    return;

  // Don't fire an event if the status hasn't changed
  if (mWasOffline == NS_IsOffline()) {
    return;
  }

  mWasOffline = !mWasOffline;

  nsAutoString name;
  if (mWasOffline) {
    name.AssignLiteral("offline");
  } else {
    name.AssignLiteral("online");
  }
  nsContentUtils::DispatchTrustedEvent(mDoc,
                                       static_cast<EventTarget*>(this),
                                       name,
                                       false,
                                       false);
}

class NotifyIdleObserverRunnable : public Runnable
{
public:
  NotifyIdleObserverRunnable(nsIIdleObserver* aIdleObserver,
                             uint32_t aTimeInS,
                             bool aCallOnidle,
                             nsGlobalWindowInner* aIdleWindow)
    : mozilla::Runnable("NotifyIdleObserverRunnable")
    , mIdleObserver(aIdleObserver)
    , mTimeInS(aTimeInS)
    , mIdleWindow(aIdleWindow)
    , mCallOnidle(aCallOnidle)
  { }

  NS_IMETHOD Run() override
  {
    if (mIdleWindow->ContainsIdleObserver(mIdleObserver, mTimeInS)) {
      return mCallOnidle ? mIdleObserver->Onidle() : mIdleObserver->Onactive();
    }
    return NS_OK;
  }

private:
  nsCOMPtr<nsIIdleObserver> mIdleObserver;
  uint32_t mTimeInS;
  RefPtr<nsGlobalWindowInner> mIdleWindow;

  // If false then call on active
  bool mCallOnidle;
};

void
nsGlobalWindowInner::NotifyIdleObserver(IdleObserverHolder* aIdleObserverHolder,
                                        bool aCallOnidle)
{
  MOZ_ASSERT(aIdleObserverHolder);
  aIdleObserverHolder->mPrevNotificationIdle = aCallOnidle;

  nsCOMPtr<nsIRunnable> caller =
    new NotifyIdleObserverRunnable(aIdleObserverHolder->mIdleObserver,
                                   aIdleObserverHolder->mTimeInS,
                                   aCallOnidle, this);
  if (NS_FAILED(Dispatch(TaskCategory::Other, caller.forget()))) {
    NS_WARNING("Failed to dispatch thread for idle observer notification.");
  }
}

bool
nsGlobalWindowInner::ContainsIdleObserver(nsIIdleObserver* aIdleObserver, uint32_t aTimeInS)
{
  MOZ_ASSERT(aIdleObserver, "Idle observer not instantiated.");
  bool found = false;
  nsTObserverArray<IdleObserverHolder>::ForwardIterator iter(mIdleObservers);
  while (iter.HasMore()) {
    IdleObserverHolder& idleObserver = iter.GetNext();
    if (idleObserver.mIdleObserver == aIdleObserver &&
        idleObserver.mTimeInS == aTimeInS) {
      found = true;
      break;
    }
  }
  return found;
}

void
IdleActiveTimerCallback(nsITimer* aTimer, void* aClosure)
{
  RefPtr<nsGlobalWindowInner> idleWindow =
    static_cast<nsGlobalWindowInner*>(aClosure);
  MOZ_ASSERT(idleWindow, "Idle window has not been instantiated.");
  idleWindow->HandleIdleActiveEvent();
}

void
IdleObserverTimerCallback(nsITimer* aTimer, void* aClosure)
{
  RefPtr<nsGlobalWindowInner> idleWindow =
    static_cast<nsGlobalWindowInner*>(aClosure);
  MOZ_ASSERT(idleWindow, "Idle window has not been instantiated.");
  idleWindow->HandleIdleObserverCallback();
}

void
nsGlobalWindowInner::HandleIdleObserverCallback()
{
  MOZ_ASSERT(static_cast<uint32_t>(mIdleCallbackIndex) < mIdleObservers.Length(),
                                  "Idle callback index exceeds array bounds!");
  IdleObserverHolder& idleObserver = mIdleObservers.ElementAt(mIdleCallbackIndex);
  NotifyIdleObserver(&idleObserver, true);
  mIdleCallbackIndex++;
  if (NS_FAILED(ScheduleNextIdleObserverCallback())) {
    NS_WARNING("Failed to set next idle observer callback.");
  }
}

nsresult
nsGlobalWindowInner::ScheduleNextIdleObserverCallback()
{
  MOZ_ASSERT(mIdleService, "No idle service!");

  if (mIdleCallbackIndex < 0 ||
      static_cast<uint32_t>(mIdleCallbackIndex) >= mIdleObservers.Length()) {
    return NS_OK;
  }

  IdleObserverHolder& idleObserver =
    mIdleObservers.ElementAt(mIdleCallbackIndex);

  uint32_t userIdleTimeMS = 0;
  nsresult rv = mIdleService->GetIdleTime(&userIdleTimeMS);
  NS_ENSURE_SUCCESS(rv, rv);

  uint32_t callbackTimeMS = 0;
  if (idleObserver.mTimeInS * 1000 + mIdleFuzzFactor > userIdleTimeMS) {
    callbackTimeMS = idleObserver.mTimeInS * 1000 - userIdleTimeMS + mIdleFuzzFactor;
  }

  mIdleTimer->Cancel();
  rv = mIdleTimer->InitWithNamedFuncCallback(
    IdleObserverTimerCallback,
    this,
    callbackTimeMS,
    nsITimer::TYPE_ONE_SHOT,
    "nsGlobalWindowInner::ScheduleNextIdleObserverCallback");
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

uint32_t
nsGlobalWindowInner::GetFuzzTimeMS()
{
  if (gIdleObserversAPIFuzzTimeDisabled) {
    return 0;
  }

  uint32_t randNum = MAX_IDLE_FUZZ_TIME_MS;
  size_t nbytes = PR_GetRandomNoise(&randNum, sizeof(randNum));
  if (nbytes != sizeof(randNum)) {
    NS_WARNING("PR_GetRandomNoise(...) Not implemented or no available noise!");
    return MAX_IDLE_FUZZ_TIME_MS;
  }

  if (randNum > MAX_IDLE_FUZZ_TIME_MS) {
    randNum %= MAX_IDLE_FUZZ_TIME_MS;
  }

  return randNum;
}

nsresult
nsGlobalWindowInner::ScheduleActiveTimerCallback()
{
  if (!mAddActiveEventFuzzTime) {
    return HandleIdleActiveEvent();
  }

  MOZ_ASSERT(mIdleTimer);
  mIdleTimer->Cancel();

  uint32_t fuzzFactorInMS = GetFuzzTimeMS();
  nsresult rv = mIdleTimer->InitWithNamedFuncCallback(
    IdleActiveTimerCallback,
    this,
    fuzzFactorInMS,
    nsITimer::TYPE_ONE_SHOT,
    "nsGlobalWindowInner::ScheduleActiveTimerCallback");
  NS_ENSURE_SUCCESS(rv, rv);
  return NS_OK;
}

nsresult
nsGlobalWindowInner::HandleIdleActiveEvent()
{
  if (mCurrentlyIdle) {
    mIdleCallbackIndex = 0;
    mIdleFuzzFactor = GetFuzzTimeMS();
    nsresult rv = ScheduleNextIdleObserverCallback();
    NS_ENSURE_SUCCESS(rv, rv);
    return NS_OK;
  }

  mIdleCallbackIndex = -1;
  MOZ_ASSERT(mIdleTimer);
  mIdleTimer->Cancel();
  nsTObserverArray<IdleObserverHolder>::ForwardIterator iter(mIdleObservers);
  while (iter.HasMore()) {
    IdleObserverHolder& idleObserver = iter.GetNext();
    if (idleObserver.mPrevNotificationIdle) {
      NotifyIdleObserver(&idleObserver, false);
    }
  }

  return NS_OK;
}

nsGlobalWindowInner::SlowScriptResponse
nsGlobalWindowInner::ShowSlowScriptDialog(const nsString& aAddonId)
{
  nsresult rv;
  AutoJSContext cx;

  if (Preferences::GetBool("dom.always_stop_slow_scripts")) {
    return KillSlowScript;
  }

  // If it isn't safe to run script, then it isn't safe to bring up the prompt
  // (since that spins the event loop). In that (rare) case, we just kill the
  // script and report a warning.
  if (!nsContentUtils::IsSafeToRunScript()) {
    JS_ReportWarningASCII(cx, "A long running script was terminated");
    return KillSlowScript;
  }

  // If our document is not active, just kill the script: we've been unloaded
  if (!HasActiveDocument()) {
    return KillSlowScript;
  }

  // Check if we should offer the option to debug
  JS::AutoFilename filename;
  unsigned lineno;
  // Computing the line number can be very expensive (see bug 1330231 for
  // example), and we don't use the line number anywhere except than in the
  // parent process, so we avoid computing it elsewhere.  This gives us most of
  // the wins we are interested in, since the source of the slowness here is
  // minified scripts which is more common in Web content that is loaded in the
  // content process.
  unsigned* linenop = XRE_IsParentProcess() ? &lineno : nullptr;
  bool hasFrame = JS::DescribeScriptedCaller(cx, &filename, linenop);

  // Record the slow script event if we haven't done so already for this inner window
  // (which represents a particular page to the user).
  if (!mHasHadSlowScript) {
    Telemetry::Accumulate(Telemetry::SLOW_SCRIPT_PAGE_COUNT, 1);
  }
  mHasHadSlowScript = true;

  if (XRE_IsContentProcess() &&
      ProcessHangMonitor::Get()) {
    ProcessHangMonitor::SlowScriptAction action;
    RefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get();
    nsIDocShell* docShell = GetDocShell();
    nsCOMPtr<nsITabChild> child = docShell ? docShell->GetTabChild() : nullptr;
    action = monitor->NotifySlowScript(child,
                                       filename.get(),
                                       aAddonId);
    if (action == ProcessHangMonitor::Terminate) {
      return KillSlowScript;
    }
    if (action == ProcessHangMonitor::TerminateGlobal) {
      return KillScriptGlobal;
    }

    if (action == ProcessHangMonitor::StartDebugger) {
      // Spin a nested event loop so that the debugger in the parent can fetch
      // any information it needs. Once the debugger has started, return to the
      // script.
      RefPtr<nsGlobalWindowOuter> outer = GetOuterWindowInternal();
      outer->EnterModalState();
      SpinEventLoopUntil([&]() { return monitor->IsDebuggerStartupComplete(); });
      outer->LeaveModalState();
      return ContinueSlowScript;
    }

    return ContinueSlowScriptAndKeepNotifying;
  }

  // Reached only on non-e10s - once per slow script dialog.
  // On e10s - we probe once at ProcessHangsMonitor.jsm
  Telemetry::Accumulate(Telemetry::SLOW_SCRIPT_NOTICE_COUNT, 1);

  // Get the nsIPrompt interface from the docshell
  nsCOMPtr<nsIDocShell> ds = GetDocShell();
  NS_ENSURE_TRUE(ds, KillSlowScript);
  nsCOMPtr<nsIPrompt> prompt = do_GetInterface(ds);
  NS_ENSURE_TRUE(prompt, KillSlowScript);

  // Prioritize the SlowScriptDebug interface over JSD1.
  nsCOMPtr<nsISlowScriptDebugCallback> debugCallback;

  if (hasFrame) {
    const char *debugCID = "@mozilla.org/dom/slow-script-debug;1";
    nsCOMPtr<nsISlowScriptDebug> debugService = do_GetService(debugCID, &rv);
    if (NS_SUCCEEDED(rv)) {
      debugService->GetActivationHandler(getter_AddRefs(debugCallback));
    }
  }

  bool failed = false;
  auto getString = [&] (const char* name,
                        nsContentUtils::PropertiesFile propFile = nsContentUtils::eDOM_PROPERTIES) {
    nsAutoString result;
    nsresult rv = nsContentUtils::GetLocalizedString(
      propFile, name, result);

    // GetStringFromName can return NS_OK and still give nullptr string
    failed = failed || NS_FAILED(rv) || result.IsEmpty();
    return result;
  };

  bool isAddonScript = !aAddonId.IsEmpty();
  bool showDebugButton = debugCallback && !isAddonScript;

  // Get localizable strings

  nsAutoString title, checkboxMsg, debugButton, msg;
  if (isAddonScript) {
    title = getString("KillAddonScriptTitle");
    checkboxMsg = getString("KillAddonScriptGlobalMessage");

    auto appName = getString("brandShortName", nsContentUtils::eBRAND_PROPERTIES);

    nsCOMPtr<nsIAddonPolicyService> aps = do_GetService("@mozilla.org/addons/policy-service;1");
    nsString addonName;
    if (!aps || NS_FAILED(aps->GetExtensionName(aAddonId, addonName))) {
      addonName = aAddonId;
    }

    const char16_t* params[] = {addonName.get(), appName.get()};
    rv = nsContentUtils::FormatLocalizedString(
        nsContentUtils::eDOM_PROPERTIES, "KillAddonScriptMessage",
        params, msg);

    failed = failed || NS_FAILED(rv);
  } else {
    title = getString("KillScriptTitle");
    checkboxMsg = getString("DontAskAgain");

    if (showDebugButton) {
      debugButton = getString("DebugScriptButton");
      msg = getString("KillScriptWithDebugMessage");
    } else {
      msg = getString("KillScriptMessage");
    }
  }

  auto stopButton = getString("StopScriptButton");
  auto waitButton = getString("WaitForScriptButton");

  if (failed) {
    NS_ERROR("Failed to get localized strings.");
    return ContinueSlowScript;
  }

  // Append file and line number information, if available
  if (filename.get()) {
    nsAutoString scriptLocation;
    // We want to drop the middle part of too-long locations.  We'll
    // define "too-long" as longer than 60 UTF-16 code units.  Just
    // have to be a bit careful about unpaired surrogates.
    NS_ConvertUTF8toUTF16 filenameUTF16(filename.get());
    if (filenameUTF16.Length() > 60) {
      // XXXbz Do we need to insert any bidi overrides here?
      size_t cutStart = 30;
      size_t cutLength = filenameUTF16.Length() - 60;
      MOZ_ASSERT(cutLength > 0);
      if (NS_IS_LOW_SURROGATE(filenameUTF16[cutStart])) {
        // Don't truncate before the low surrogate, in case it's preceded by a
        // high surrogate and forms a single Unicode character.  Instead, just
        // include the low surrogate.
        ++cutStart;
        --cutLength;
      }
      if (NS_IS_LOW_SURROGATE(filenameUTF16[cutStart + cutLength])) {
        // Likewise, don't drop a trailing low surrogate here.  We want to
        // increase cutLength, since it might be 0 already so we can't very well
        // decrease it.
        ++cutLength;
      }

      // Insert U+2026 HORIZONTAL ELLIPSIS
      filenameUTF16.ReplaceLiteral(cutStart, cutLength, u"\x2026");
    }
    const char16_t *formatParams[] = { filenameUTF16.get() };
    rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
                                               "KillScriptLocation",
                                               formatParams,
                                               scriptLocation);

    if (NS_SUCCEEDED(rv)) {
      msg.AppendLiteral("\n\n");
      msg.Append(scriptLocation);
      msg.Append(':');
      msg.AppendInt(lineno);
    }
  }

  uint32_t buttonFlags = nsIPrompt::BUTTON_POS_1_DEFAULT +
                         (nsIPrompt::BUTTON_TITLE_IS_STRING *
                          (nsIPrompt::BUTTON_POS_0 + nsIPrompt::BUTTON_POS_1));

  // Add a third button if necessary.
  if (showDebugButton)
    buttonFlags += nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2;

  bool checkboxValue = false;
  int32_t buttonPressed = 0; // In case the user exits dialog by clicking X.
  {
    // Null out the operation callback while we're re-entering JS here.
    AutoDisableJSInterruptCallback disabler(cx);

    // Open the dialog.
    rv = prompt->ConfirmEx(title.get(), msg.get(), buttonFlags,
                           waitButton.get(), stopButton.get(),
                           debugButton.get(), checkboxMsg.get(),
                           &checkboxValue, &buttonPressed);
  }

  if (buttonPressed == 0) {
    if (checkboxValue && !isAddonScript && NS_SUCCEEDED(rv))
      return AlwaysContinueSlowScript;
    return ContinueSlowScript;
  }

  if (buttonPressed == 2) {
    MOZ_RELEASE_ASSERT(debugCallback);

    rv = debugCallback->HandleSlowScriptDebug(this);
    return NS_SUCCEEDED(rv) ? ContinueSlowScript : KillSlowScript;
  }

  JS_ClearPendingException(cx);

  if (checkboxValue && isAddonScript)
    return KillScriptGlobal;
  return KillSlowScript;
}

uint32_t
nsGlobalWindowInner::FindInsertionIndex(IdleObserverHolder* aIdleObserver)
{
  MOZ_ASSERT(aIdleObserver, "Idle observer not instantiated.");

  uint32_t i = 0;
  nsTObserverArray<IdleObserverHolder>::ForwardIterator iter(mIdleObservers);
  while (iter.HasMore()) {
    IdleObserverHolder& idleObserver = iter.GetNext();
    if (idleObserver.mTimeInS > aIdleObserver->mTimeInS) {
      break;
    }
    i++;
    MOZ_ASSERT(i <= mIdleObservers.Length(), "Array index out of bounds error.");
  }

  return i;
}

nsresult
nsGlobalWindowInner::RegisterIdleObserver(nsIIdleObserver* aIdleObserver)
{
  nsresult rv;
  if (mIdleObservers.IsEmpty()) {
    mIdleService = do_GetService("@mozilla.org/widget/idleservice;1", &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    rv = mIdleService->AddIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S);
    NS_ENSURE_SUCCESS(rv, rv);

    if (!mIdleTimer) {
      mIdleTimer = NS_NewTimer();
      NS_ENSURE_TRUE(mIdleTimer, NS_ERROR_OUT_OF_MEMORY);
    } else {
      mIdleTimer->Cancel();
    }
  }

  MOZ_ASSERT(mIdleService);
  MOZ_ASSERT(mIdleTimer);

  IdleObserverHolder tmpIdleObserver;
  tmpIdleObserver.mIdleObserver = aIdleObserver;
  rv = aIdleObserver->GetTime(&tmpIdleObserver.mTimeInS);
  NS_ENSURE_SUCCESS(rv, rv);
  NS_ENSURE_ARG_MAX(tmpIdleObserver.mTimeInS, UINT32_MAX / 1000);
  NS_ENSURE_ARG_MIN(tmpIdleObserver.mTimeInS, MIN_IDLE_NOTIFICATION_TIME_S);

  uint32_t insertAtIndex = FindInsertionIndex(&tmpIdleObserver);
  if (insertAtIndex == mIdleObservers.Length()) {
    mIdleObservers.AppendElement(tmpIdleObserver);
  }
  else {
    mIdleObservers.InsertElementAt(insertAtIndex, tmpIdleObserver);
  }

  bool userIsIdle = false;
  rv = nsContentUtils::IsUserIdle(MIN_IDLE_NOTIFICATION_TIME_S, &userIsIdle);
  NS_ENSURE_SUCCESS(rv, rv);

  // Special case. First idle observer added to empty list while the user is idle.
  // Haven't received 'idle' topic notification from slow idle service yet.
  // Need to wait for the idle notification and then notify idle observers in the list.
  if (userIsIdle && mIdleCallbackIndex == -1) {
    return NS_OK;
  }

  if (!mCurrentlyIdle) {
    return NS_OK;
  }

  MOZ_ASSERT(mIdleCallbackIndex >= 0);

  if (static_cast<int32_t>(insertAtIndex) < mIdleCallbackIndex) {
    IdleObserverHolder& idleObserver = mIdleObservers.ElementAt(insertAtIndex);
    NotifyIdleObserver(&idleObserver, true);
    mIdleCallbackIndex++;
    return NS_OK;
  }

  if (static_cast<int32_t>(insertAtIndex) == mIdleCallbackIndex) {
    mIdleTimer->Cancel();
    rv = ScheduleNextIdleObserverCallback();
    NS_ENSURE_SUCCESS(rv, rv);
  }
  return NS_OK;
}

nsresult
nsGlobalWindowInner::FindIndexOfElementToRemove(nsIIdleObserver* aIdleObserver,
                                                int32_t* aRemoveElementIndex)
{
  MOZ_ASSERT(aIdleObserver, "Idle observer not instantiated.");

  *aRemoveElementIndex = 0;
  if (mIdleObservers.IsEmpty()) {
    return NS_ERROR_FAILURE;
  }

  uint32_t aIdleObserverTimeInS;
  nsresult rv = aIdleObserver->GetTime(&aIdleObserverTimeInS);
  NS_ENSURE_SUCCESS(rv, rv);
  NS_ENSURE_ARG_MIN(aIdleObserverTimeInS, MIN_IDLE_NOTIFICATION_TIME_S);

  nsTObserverArray<IdleObserverHolder>::ForwardIterator iter(mIdleObservers);
  while (iter.HasMore()) {
    IdleObserverHolder& idleObserver = iter.GetNext();
    if (idleObserver.mTimeInS == aIdleObserverTimeInS &&
        idleObserver.mIdleObserver == aIdleObserver ) {
      break;
    }
    (*aRemoveElementIndex)++;
  }
  return static_cast<uint32_t>(*aRemoveElementIndex) >= mIdleObservers.Length() ?
    NS_ERROR_FAILURE : NS_OK;
}

nsresult
nsGlobalWindowInner::UnregisterIdleObserver(nsIIdleObserver* aIdleObserver)
{
  int32_t removeElementIndex;
  nsresult rv = FindIndexOfElementToRemove(aIdleObserver, &removeElementIndex);
  if (NS_FAILED(rv)) {
    NS_WARNING("Idle observer not found in list of idle observers. No idle observer removed.");
    return NS_OK;
  }
  mIdleObservers.RemoveElementAt(removeElementIndex);

  MOZ_ASSERT(mIdleTimer);
  if (mIdleObservers.IsEmpty() && mIdleService) {
    rv = mIdleService->RemoveIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S);
    NS_ENSURE_SUCCESS(rv, rv);
    mIdleService = nullptr;

    mIdleTimer->Cancel();
    mIdleCallbackIndex = -1;
    return NS_OK;
  }

  if (!mCurrentlyIdle) {
    return NS_OK;
  }

  if (removeElementIndex < mIdleCallbackIndex) {
    mIdleCallbackIndex--;
    return NS_OK;
  }

  if (removeElementIndex != mIdleCallbackIndex) {
    return NS_OK;
  }

  mIdleTimer->Cancel();

  // If the last element in the array had been notified then decrement
  // mIdleCallbackIndex because an idle was removed from the list of
  // idle observers.
  // Example: add idle observer with time 1, 2, 3,
  // Idle notifications for idle observers with time 1, 2, 3 are complete
  // Remove idle observer with time 3 while the user is still idle.
  // The user never transitioned to active state.
  // Add an idle observer with idle time 4
  if (static_cast<uint32_t>(mIdleCallbackIndex) == mIdleObservers.Length()) {
    mIdleCallbackIndex--;
  }
  rv = ScheduleNextIdleObserverCallback();
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

nsresult
nsGlobalWindowInner::Observe(nsISupports* aSubject, const char* aTopic,
                             const char16_t* aData)
{
  if (!nsCRT::strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC)) {
    if (!IsFrozen()) {
        // Fires an offline status event if the offline status has changed
        FireOfflineStatusEventIfChanged();
    }
    return NS_OK;
  }

  if (!nsCRT::strcmp(aTopic, MEMORY_PRESSURE_OBSERVER_TOPIC)) {
    if (mPerformance) {
      mPerformance->MemoryPressure();
    }
    return NS_OK;
  }

  if (!nsCRT::strcmp(aTopic, "clear-site-data-reload-needed")) {
    // The reload is propagated from the top-level window only.
    NS_ConvertUTF16toUTF8 otherOrigin(aData);
    PropagateClearSiteDataReload(otherOrigin);
    return NS_OK;
  }

  if (!nsCRT::strcmp(aTopic, OBSERVER_TOPIC_IDLE)) {
    mCurrentlyIdle = true;
    if (IsFrozen()) {
      // need to fire only one idle event while the window is frozen.
      mNotifyIdleObserversIdleOnThaw = true;
      mNotifyIdleObserversActiveOnThaw = false;
    } else if (IsCurrentInnerWindow()) {
      HandleIdleActiveEvent();
    }
    return NS_OK;
  }

  if (!nsCRT::strcmp(aTopic, OBSERVER_TOPIC_ACTIVE)) {
    mCurrentlyIdle = false;
    if (IsFrozen()) {
      mNotifyIdleObserversActiveOnThaw = true;
      mNotifyIdleObserversIdleOnThaw = false;
    } else if (IsCurrentInnerWindow()) {
      ScheduleActiveTimerCallback();
    }
    return NS_OK;
  }

  if (!nsCRT::strcmp(aTopic, "offline-cache-update-added")) {
    if (mApplicationCache)
      return NS_OK;

    // Instantiate the application object now. It observes update belonging to
    // this window's document and correctly updates the applicationCache object
    // state.
    nsCOMPtr<nsIDOMOfflineResourceList> applicationCache = GetApplicationCache();
    nsCOMPtr<nsIObserver> observer = do_QueryInterface(applicationCache);
    if (observer)
      observer->Observe(aSubject, aTopic, aData);

    return NS_OK;
  }

  if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
    MOZ_ASSERT(!NS_strcmp(aData, u"intl.accept_languages"));

    // The user preferred languages have changed, we need to fire an event on
    // Window object and invalidate the cache for navigator.languages. It is
    // done for every change which can be a waste of cycles but those should be
    // fairly rare.
    // We MUST invalidate navigator.languages before sending the event in the
    // very likely situation where an event handler will try to read its value.

    if (mNavigator) {
      NavigatorBinding::ClearCachedLanguageValue(mNavigator);
      NavigatorBinding::ClearCachedLanguagesValue(mNavigator);
    }

    // The event has to be dispatched only to the current inner window.
    if (!IsCurrentInnerWindow()) {
      return NS_OK;
    }

    RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
    event->InitEvent(NS_LITERAL_STRING("languagechange"), false, false);
    event->SetTrusted(true);

    ErrorResult rv;
    DispatchEvent(*event, rv);
    return rv.StealNSResult();
  }

  NS_WARNING("unrecognized topic in nsGlobalWindowInner::Observe");
  return NS_ERROR_FAILURE;
}

void
nsGlobalWindowInner::ObserveStorageNotification(StorageEvent* aEvent,
                                                const char16_t* aStorageType,
                                                bool aPrivateBrowsing)
{
  MOZ_ASSERT(aEvent);

  // The private browsing check must be done here again because this window
  // could have changed its state before the notification check and now. This
  // happens in case this window did have a docShell at that time.
  if (aPrivateBrowsing != IsPrivateBrowsing()) {
    return;
  }

  // LocalStorage can only exist on an inner window, and we don't want to
  // generate events on frozen or otherwise-navigated-away from windows.
  // (Actually, this code used to try and buffer events for frozen windows,
  // but it never worked, so we've removed it.  See bug 1285898.)
  if (!IsCurrentInnerWindow() || IsFrozen()) {
    return;
  }

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

  bool fireMozStorageChanged = false;
  nsAutoString eventType;
  eventType.AssignLiteral("storage");

  if (!NS_strcmp(aStorageType, u"sessionStorage")) {
    nsCOMPtr<nsIDOMStorage> changingStorage = aEvent->GetStorageArea();
    MOZ_ASSERT(changingStorage);

    bool check = false;

    nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(GetDocShell());
    if (storageManager) {
      nsresult rv = storageManager->CheckStorage(principal, changingStorage,
                                                 &check);
      if (NS_FAILED(rv)) {
        return;
      }
    }

    if (!check) {
      // This storage event is not coming from our storage or is coming
      // from a different docshell, i.e. it is a clone, ignore this event.
      return;
    }

    MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
            ("nsGlobalWindowInner %p with sessionStorage %p passing event from %p",
             this, mSessionStorage.get(), changingStorage.get()));

    fireMozStorageChanged = mSessionStorage == changingStorage;
    if (fireMozStorageChanged) {
      eventType.AssignLiteral("MozSessionStorageChanged");
    }
  }

  else {
    MOZ_ASSERT(!NS_strcmp(aStorageType, u"localStorage"));

    MOZ_DIAGNOSTIC_ASSERT(StorageUtils::PrincipalsEqual(aEvent->GetPrincipal(),
                                                        principal));

    fireMozStorageChanged = mLocalStorage == aEvent->GetStorageArea();

    if (fireMozStorageChanged) {
      eventType.AssignLiteral("MozLocalStorageChanged");
    }
  }

  // Clone the storage event included in the observer notification. We want
  // to dispatch clones rather than the original event.
  IgnoredErrorResult error;
  RefPtr<StorageEvent> clonedEvent =
    CloneStorageEvent(eventType, aEvent, error);
  if (error.Failed()) {
    return;
  }

  clonedEvent->SetTrusted(true);

  if (fireMozStorageChanged) {
    WidgetEvent* internalEvent = clonedEvent->WidgetEventPtr();
    internalEvent->mFlags.mOnlyChromeDispatch = true;
  }

  DispatchEvent(*clonedEvent);
}

already_AddRefed<StorageEvent>
nsGlobalWindowInner::CloneStorageEvent(const nsAString& aType,
                                       const RefPtr<StorageEvent>& aEvent,
                                       ErrorResult& aRv)
{
  StorageEventInit dict;

  dict.mBubbles = aEvent->Bubbles();
  dict.mCancelable = aEvent->Cancelable();
  aEvent->GetKey(dict.mKey);
  aEvent->GetOldValue(dict.mOldValue);
  aEvent->GetNewValue(dict.mNewValue);
  aEvent->GetUrl(dict.mUrl);

  RefPtr<Storage> storageArea = aEvent->GetStorageArea();

  RefPtr<Storage> storage;

  // If null, this is a localStorage event received by IPC.
  if (!storageArea) {
    storage = GetLocalStorage(aRv);
    if (aRv.Failed() || !storage) {
      return nullptr;
    }

    MOZ_ASSERT(storage->Type() == Storage::eLocalStorage);
    RefPtr<LocalStorage> localStorage =
      static_cast<LocalStorage*>(storage.get());

    // We must apply the current change to the 'local' localStorage.
    localStorage->ApplyEvent(aEvent);
  } else if (storageArea->Type() == Storage::eSessionStorage) {
    storage = GetSessionStorage(aRv);
  } else {
    MOZ_ASSERT(storageArea->Type() == Storage::eLocalStorage);
    storage = GetLocalStorage(aRv);
  }

  if (aRv.Failed() || !storage) {
    return nullptr;
  }

  MOZ_ASSERT(storage);
  MOZ_ASSERT_IF(storageArea, storage->IsForkOf(storageArea));

  dict.mStorageArea = storage;

  RefPtr<StorageEvent> event = StorageEvent::Constructor(this, aType, dict);
  return event.forget();
}

void
nsGlobalWindowInner::Suspend()
{
  MOZ_ASSERT(NS_IsMainThread());

  // We can only safely suspend windows that are the current inner window.  If
  // its not the current inner, then we are in one of two different cases.
  // Either we are in the bfcache or we are doomed window that is going away.
  // When a window becomes inactive we purposely avoid placing already suspended
  // windows into the bfcache.  It only expects windows suspended due to the
  // Freeze() method which occurs while the window is still the current inner.
  // So we must not call Suspend() on bfcache windows at this point or this
  // invariant will be broken.  If the window is doomed there is no point in
  // suspending it since it will soon be gone.
  if (!IsCurrentInnerWindow()) {
    return;
  }

  // All children are also suspended.  This ensure mSuspendDepth is
  // set properly and the timers are properly canceled for each child.
  CallOnChildren(&nsGlobalWindowInner::Suspend);

  mSuspendDepth += 1;
  if (mSuspendDepth != 1) {
    return;
  }

  nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
  if (ac) {
    for (uint32_t i = 0; i < mEnabledSensors.Length(); i++)
      ac->RemoveWindowListener(mEnabledSensors[i], this);
  }
  DisableGamepadUpdates();
  DisableVRUpdates();

  SuspendWorkersForWindow(this);

  SuspendIdleRequests();

  mTimeoutManager->Suspend();

  // Suspend all of the AudioContexts for this window
  for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
    ErrorResult dummy;
    RefPtr<Promise> d = mAudioContexts[i]->Suspend(dummy);
  }
}

void
nsGlobalWindowInner::Resume()
{
  MOZ_ASSERT(NS_IsMainThread());

  // We can only safely resume a window if its the current inner window.  If
  // its not the current inner, then we are in one of two different cases.
  // Either we are in the bfcache or we are doomed window that is going away.
  // If a window is suspended when it becomes inactive we purposely do not
  // put it in the bfcache, so Resume should never be needed in that case.
  // If the window is doomed then there is no point in resuming it.
  if (!IsCurrentInnerWindow()) {
    return;
  }

  // Resume all children.  This restores timers recursively canceled
  // in Suspend() and ensures all children have the correct mSuspendDepth.
  CallOnChildren(&nsGlobalWindowInner::Resume);

  MOZ_ASSERT(mSuspendDepth != 0);
  mSuspendDepth -= 1;
  if (mSuspendDepth != 0) {
    return;
  }

  // We should not be able to resume a frozen window.  It must be Thaw()'d first.
  MOZ_ASSERT(mFreezeDepth == 0);

  nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
  if (ac) {
    for (uint32_t i = 0; i < mEnabledSensors.Length(); i++)
      ac->AddWindowListener(mEnabledSensors[i], this);
  }
  EnableGamepadUpdates();
  EnableVRUpdates();

  // Resume all of the AudioContexts for this window
  for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
    ErrorResult dummy;
    RefPtr<Promise> d = mAudioContexts[i]->Resume(dummy);
  }

  mTimeoutManager->Resume();

  ResumeIdleRequests();

  // Resume all of the workers for this window.  We must do this
  // after timeouts since workers may have queued events that can trigger
  // a setTimeout().
  ResumeWorkersForWindow(this);
}

bool
nsGlobalWindowInner::IsSuspended() const
{
  MOZ_ASSERT(NS_IsMainThread());
  return mSuspendDepth != 0;
}

void
nsGlobalWindowInner::Freeze()
{
  MOZ_ASSERT(NS_IsMainThread());
  Suspend();
  FreezeInternal();
}

void
nsGlobalWindowInner::FreezeInternal()
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_DIAGNOSTIC_ASSERT(IsCurrentInnerWindow());
  MOZ_DIAGNOSTIC_ASSERT(IsSuspended());

  CallOnChildren(&nsGlobalWindowInner::FreezeInternal);

  mFreezeDepth += 1;
  MOZ_ASSERT(mSuspendDepth >= mFreezeDepth);
  if (mFreezeDepth != 1) {
    return;
  }

  FreezeWorkersForWindow(this);

  mTimeoutManager->Freeze();
  if (mClientSource) {
    mClientSource->Freeze();
  }

  NotifyDOMWindowFrozen(this);
}

void
nsGlobalWindowInner::Thaw()
{
  MOZ_ASSERT(NS_IsMainThread());
  ThawInternal();
  Resume();
}

void
nsGlobalWindowInner::ThawInternal()
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_DIAGNOSTIC_ASSERT(IsCurrentInnerWindow());
  MOZ_DIAGNOSTIC_ASSERT(IsSuspended());

  CallOnChildren(&nsGlobalWindowInner::ThawInternal);

  MOZ_ASSERT(mFreezeDepth != 0);
  mFreezeDepth -= 1;
  MOZ_ASSERT(mSuspendDepth >= mFreezeDepth);
  if (mFreezeDepth != 0) {
    return;
  }

  if (mClientSource) {
    mClientSource->Thaw();
  }
  mTimeoutManager->Thaw();

  ThawWorkersForWindow(this);

  NotifyDOMWindowThawed(this);
}

bool
nsGlobalWindowInner::IsFrozen() const
{
  MOZ_ASSERT(NS_IsMainThread());
  bool frozen = mFreezeDepth != 0;
  MOZ_ASSERT_IF(frozen, IsSuspended());
  return frozen;
}

void
nsGlobalWindowInner::SyncStateFromParentWindow()
{
  // This method should only be called on an inner window that has been
  // assigned to an outer window already.
  MOZ_ASSERT(IsCurrentInnerWindow());
  nsPIDOMWindowOuter* outer = GetOuterWindow();
  MOZ_ASSERT(outer);

  // Attempt to find our parent windows.
  nsCOMPtr<Element> frame = outer->GetFrameElementInternal();
  nsPIDOMWindowOuter* parentOuter = frame ? frame->OwnerDoc()->GetWindow()
                                          : nullptr;
  nsGlobalWindowInner* parentInner =
    parentOuter ? nsGlobalWindowInner::Cast(parentOuter->GetCurrentInnerWindow())
                : nullptr;

  // If our outer is in a modal state, but our parent is not in a modal
  // state, then we must apply the suspend directly.  If our parent is
  // in a modal state then we should get the suspend automatically
  // via the parentSuspendDepth application below.
  if ((!parentInner || !parentInner->IsInModalState()) && IsInModalState()) {
    Suspend();
  }

  uint32_t parentFreezeDepth = parentInner ? parentInner->mFreezeDepth : 0;
  uint32_t parentSuspendDepth = parentInner ? parentInner->mSuspendDepth : 0;

  // Since every Freeze() calls Suspend(), the suspend count must
  // be equal or greater to the freeze count.
  MOZ_ASSERT(parentFreezeDepth <= parentSuspendDepth);

  // First apply the Freeze() calls.
  for (uint32_t i = 0; i < parentFreezeDepth; ++i) {
    Freeze();
  }

  // Now apply only the number of Suspend() calls to reach the target
  // suspend count after applying the Freeze() calls.
  for (uint32_t i = 0; i < (parentSuspendDepth - parentFreezeDepth); ++i) {
    Suspend();
  }
}

template<typename Method, typename... Args>
nsGlobalWindowInner::CallState
nsGlobalWindowInner::CallOnChildren(Method aMethod, Args& ...aArgs)
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(IsCurrentInnerWindow());

  CallState state = CallState::Continue;

  nsCOMPtr<nsIDocShell> docShell = GetDocShell();
  if (!docShell) {
    return state;
  }

  int32_t childCount = 0;
  docShell->GetChildCount(&childCount);

  // Take a copy of the current children so that modifications to
  // the child list don't affect to the iteration.
  AutoTArray<nsCOMPtr<nsIDocShellTreeItem>, 8> children;
  for (int32_t i = 0; i < childCount; ++i) {
    nsCOMPtr<nsIDocShellTreeItem> childShell;
    docShell->GetChildAt(i, getter_AddRefs(childShell));
    if (childShell) {
      children.AppendElement(childShell);
    }
  }

  for (nsCOMPtr<nsIDocShellTreeItem> childShell : children) {
    nsCOMPtr<nsPIDOMWindowOuter> pWin = childShell->GetWindow();
    if (!pWin) {
      continue;
    }

    auto* win = nsGlobalWindowOuter::Cast(pWin);
    nsGlobalWindowInner* inner = win->GetCurrentInnerWindowInternal();

    // This is a bit hackish. Only freeze/suspend windows which are truly our
    // subwindows.
    nsCOMPtr<Element> frame = pWin->GetFrameElementInternal();
    if (!mDoc || !frame || mDoc != frame->OwnerDoc() || !inner) {
      continue;
    }

    // Call the child method using our helper CallChild() template method.
    // This allows us to handle both void returning methods and methods
    // that return CallState explicitly.  For void returning methods we
    // assume CallState::Continue.
    typedef decltype((inner->*aMethod)(aArgs...)) returnType;
    state = CallChild<returnType>(inner, aMethod, aArgs...);

    if (state == CallState::Stop) {
      return state;
    }
  }

  return state;
}

Maybe<ClientInfo>
nsGlobalWindowInner::GetClientInfo() const
{
  MOZ_ASSERT(NS_IsMainThread());
  Maybe<ClientInfo> clientInfo;
  if (mClientSource) {
    clientInfo.emplace(mClientSource->Info());
  }
  return clientInfo;
}

Maybe<ClientState>
nsGlobalWindowInner::GetClientState() const
{
  MOZ_ASSERT(NS_IsMainThread());
  Maybe<ClientState> clientState;
  if (mClientSource) {
    ClientState state;
    nsresult rv = mClientSource->SnapshotState(&state);
    if (NS_SUCCEEDED(rv)) {
      clientState.emplace(state);
    }
  }
  return clientState;
}

Maybe<ServiceWorkerDescriptor>
nsGlobalWindowInner::GetController() const
{
  MOZ_ASSERT(NS_IsMainThread());
  Maybe<ServiceWorkerDescriptor> controller;
  if (mClientSource) {
    controller = mClientSource->GetController();
  }
  return controller;
}

RefPtr<ServiceWorker>
nsGlobalWindowInner::GetOrCreateServiceWorker(const ServiceWorkerDescriptor& aDescriptor)
{
  MOZ_ASSERT(NS_IsMainThread());
  RefPtr<ServiceWorker> ref;
  ForEachEventTargetObject([&] (DOMEventTargetHelper* aTarget, bool* aDoneOut) {
    RefPtr<ServiceWorker> sw = do_QueryObject(aTarget);
    if (!sw || !sw->Descriptor().Matches(aDescriptor)) {
      return;
    }

    ref = sw.forget();
    *aDoneOut = true;
  });

  if (!ref) {
    ref = ServiceWorker::Create(this, aDescriptor);
  }

  return ref.forget();
}

RefPtr<ServiceWorkerRegistration>
nsGlobalWindowInner::GetOrCreateServiceWorkerRegistration(const ServiceWorkerRegistrationDescriptor& aDescriptor)
{
  MOZ_ASSERT(NS_IsMainThread());
  RefPtr<ServiceWorkerRegistration> ref;
  ForEachEventTargetObject([&] (DOMEventTargetHelper* aTarget, bool* aDoneOut) {
    RefPtr<ServiceWorkerRegistration> swr = do_QueryObject(aTarget);
    if (!swr || !swr->MatchesDescriptor(aDescriptor)) {
      return;
    }

    ref = swr.forget();
    *aDoneOut = true;
  });

  if (!ref) {
    ref = ServiceWorkerRegistration::CreateForMainThread(this, aDescriptor);
  }

  return ref.forget();
}

nsresult
nsGlobalWindowInner::FireDelayedDOMEvents()
{
  if (mApplicationCache) {
    static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->FirePendingEvents();
  }

  // Fires an offline status event if the offline status has changed
  FireOfflineStatusEventIfChanged();

  if (mNotifyIdleObserversIdleOnThaw) {
    mNotifyIdleObserversIdleOnThaw = false;
    HandleIdleActiveEvent();
  }

  if (mNotifyIdleObserversActiveOnThaw) {
    mNotifyIdleObserversActiveOnThaw = false;
    ScheduleActiveTimerCallback();
  }

  nsCOMPtr<nsIDocShell> docShell = GetDocShell();
  if (docShell) {
    int32_t childCount = 0;
    docShell->GetChildCount(&childCount);

    // Take a copy of the current children so that modifications to
    // the child list don't affect to the iteration.
    AutoTArray<nsCOMPtr<nsIDocShellTreeItem>, 8> children;
    for (int32_t i = 0; i < childCount; ++i) {
      nsCOMPtr<nsIDocShellTreeItem> childShell;
      docShell->GetChildAt(i, getter_AddRefs(childShell));
      if (childShell) {
        children.AppendElement(childShell);
      }
    }

    for (nsCOMPtr<nsIDocShellTreeItem> childShell : children) {
      if (nsCOMPtr<nsPIDOMWindowOuter> pWin = childShell->GetWindow()) {
        auto* win = nsGlobalWindowOuter::Cast(pWin);
        win->FireDelayedDOMEvents();
      }
    }
  }

  return NS_OK;
}

//*****************************************************************************
// nsGlobalWindowInner: Window Control Functions
//*****************************************************************************

nsPIDOMWindowOuter*
nsGlobalWindowInner::GetParentInternal()
{
  nsGlobalWindowOuter* outer = GetOuterWindowInternal();
  if (!outer) {
    // No outer window available!
    return nullptr;
  }
  return outer->GetParentInternal();
}

//*****************************************************************************
// nsGlobalWindowInner: Timeout Functions
//*****************************************************************************

nsGlobalWindowInner*
nsGlobalWindowInner::InnerForSetTimeoutOrInterval(ErrorResult& aError)
{
  nsGlobalWindowOuter* outer = GetOuterWindowInternal();
  nsGlobalWindowInner* currentInner = outer ? outer->GetCurrentInnerWindowInternal() : this;

  // If forwardTo is not the window with an active document then we want the
  // call to setTimeout/Interval to be a noop, so return null but don't set an
  // error.
  return HasActiveDocument() ? currentInner : nullptr;
}

int32_t
nsGlobalWindowInner::SetTimeout(JSContext* aCx, Function& aFunction,
                                int32_t aTimeout,
                                const Sequence<JS::Value>& aArguments,
                                ErrorResult& aError)
{
  return SetTimeoutOrInterval(aCx, aFunction, aTimeout, aArguments, false,
                              aError);
}

int32_t
nsGlobalWindowInner::SetTimeout(JSContext* aCx, const nsAString& aHandler,
                                int32_t aTimeout,
                                const Sequence<JS::Value>& /* unused */,
                                ErrorResult& aError)
{
  return SetTimeoutOrInterval(aCx, aHandler, aTimeout, false, aError);
}

int32_t
nsGlobalWindowInner::SetInterval(JSContext* aCx, Function& aFunction,
                                 const int32_t aTimeout,
                                 const Sequence<JS::Value>& aArguments,
                                 ErrorResult& aError)
{
  return SetTimeoutOrInterval(
    aCx, aFunction, aTimeout, aArguments, true, aError);
}

int32_t
nsGlobalWindowInner::SetInterval(JSContext* aCx, const nsAString& aHandler,
                                 const int32_t aTimeout,
                                 const Sequence<JS::Value>& /* unused */,
                                 ErrorResult& aError)
{
  return SetTimeoutOrInterval(aCx, aHandler, aTimeout, true, aError);
}

int32_t
nsGlobalWindowInner::SetTimeoutOrInterval(JSContext *aCx, Function& aFunction,
                                          int32_t aTimeout,
                                          const Sequence<JS::Value>& aArguments,
                                          bool aIsInterval, ErrorResult& aError)
{
  nsGlobalWindowInner* inner = InnerForSetTimeoutOrInterval(aError);
  if (!inner) {
    return -1;
  }

  if (inner != this) {
    return inner->SetTimeoutOrInterval(aCx, aFunction, aTimeout, aArguments,
                                       aIsInterval, aError);
  }

  nsCOMPtr<nsIScriptTimeoutHandler> handler =
    NS_CreateJSTimeoutHandler(aCx, this, aFunction, aArguments, aError);
  if (!handler) {
    return 0;
  }

  int32_t result;
  aError = mTimeoutManager->SetTimeout(handler, aTimeout, aIsInterval,
                                      Timeout::Reason::eTimeoutOrInterval,
                                      &result);
  return result;
}

int32_t
nsGlobalWindowInner::SetTimeoutOrInterval(JSContext* aCx, const nsAString& aHandler,
                                          int32_t aTimeout, bool aIsInterval,
                                          ErrorResult& aError)
{
  nsGlobalWindowInner* inner = InnerForSetTimeoutOrInterval(aError);
  if (!inner) {
    return -1;
  }

  if (inner != this) {
    return inner->SetTimeoutOrInterval(aCx, aHandler, aTimeout, aIsInterval,
                                       aError);
  }

  nsCOMPtr<nsIScriptTimeoutHandler> handler =
    NS_CreateJSTimeoutHandler(aCx, this, aHandler, aError);
  if (!handler) {
    return 0;
  }

  int32_t result;
  aError = mTimeoutManager->SetTimeout(handler, aTimeout, aIsInterval,
                                      Timeout::Reason::eTimeoutOrInterval,
                                      &result);
  return result;
}

bool
nsGlobalWindowInner::RunTimeoutHandler(Timeout* aTimeout,
                                       nsIScriptContext* aScx)
{
  // Hold on to the timeout in case mExpr or mFunObj releases its
  // doc.
  RefPtr<Timeout> timeout = aTimeout;
  Timeout* last_running_timeout = mTimeoutManager->BeginRunningTimeout(timeout);
  timeout->mRunning = true;

  // Push this timeout's popup control state, which should only be
  // eabled the first time a timeout fires that was created while
  // popups were enabled and with a delay less than
  // "dom.disable_open_click_delay".
  nsAutoPopupStatePusher popupStatePusher(timeout->mPopupState);

  // Clear the timeout's popup state, if any, to prevent interval
  // timeouts from repeatedly opening poups.
  timeout->mPopupState = openAbused;

  bool trackNestingLevel = !timeout->mIsInterval;
  uint32_t nestingLevel;
  if (trackNestingLevel) {
    nestingLevel = TimeoutManager::GetNestingLevel();
    TimeoutManager::SetNestingLevel(timeout->mNestingLevel);
  }

  const char *reason;
  if (timeout->mIsInterval) {
    reason = "setInterval handler";
  } else {
    reason = "setTimeout handler";
  }

  bool abortIntervalHandler = false;
  nsCOMPtr<nsIScriptTimeoutHandler> handler(do_QueryInterface(timeout->mScriptHandler));
  if (handler) {
    RefPtr<Function> callback = handler->GetCallback();

    if (!callback) {
      // Evaluate the timeout expression.
      const nsAString& script = handler->GetHandlerText();

      const char* filename = nullptr;
      uint32_t lineNo = 0, dummyColumn = 0;
      handler->GetLocation(&filename, &lineNo, &dummyColumn);

      // New script entry point required, due to the "Create a script" sub-step of
      // http://www.whatwg.org/specs/web-apps/current-work/#timer-initialisation-steps
      nsAutoMicroTask mt;
      AutoEntryScript aes(this, reason, true);
      JS::CompileOptions options(aes.cx());
      options.setFileAndLine(filename, lineNo);
      options.setNoScriptRval(true);
      JS::Rooted<JSObject*> global(aes.cx(), FastGetGlobalJSObject());
      nsresult rv = NS_OK;
      {
        nsJSUtils::ExecutionContext exec(aes.cx(), global);
        rv = exec.CompileAndExec(options, script);
      }

      if (rv == NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) {
        abortIntervalHandler = true;
      }
    } else {
      // Hold strong ref to ourselves while we call the callback.
      nsCOMPtr<nsISupports> me(static_cast<nsIDOMWindow*>(this));
      ErrorResult rv;
      JS::Rooted<JS::Value> ignoredVal(RootingCx());
      callback->Call(me, handler->GetArgs(), &ignoredVal, rv, reason);
      if (rv.IsUncatchableException()) {
        abortIntervalHandler = true;
      }

      rv.SuppressException();
    }
  } else {
    nsCOMPtr<nsITimeoutHandler> basicHandler(timeout->mScriptHandler);
    nsCOMPtr<nsISupports> kungFuDeathGrip(static_cast<nsIDOMWindow*>(this));
    mozilla::Unused << kungFuDeathGrip;
    basicHandler->Call();
  }

  // If we received an uncatchable exception, do not schedule the timeout again.
  // This allows the slow script dialog to break easy DoS attacks like
  // setInterval(function() { while(1); }, 100);
  if (abortIntervalHandler) {
    // If it wasn't an interval timer to begin with, this does nothing.  If it
    // was, we'll treat it as a timeout that we just ran and discard it when
    // we return.
    timeout->mIsInterval = false;
   }

  // We ignore any failures from calling EvaluateString() on the context or
  // Call() on a Function here since we're in a loop
  // where we're likely to be running timeouts whose OS timers
  // didn't fire in time and we don't want to not fire those timers
  // now just because execution of one timer failed. We can't
  // propagate the error to anyone who cares about it from this
  // point anyway, and the script context should have already reported
  // the script error in the usual way - so we just drop it.

  if (trackNestingLevel) {
    TimeoutManager::SetNestingLevel(nestingLevel);
  }

  mTimeoutManager->EndRunningTimeout(last_running_timeout);
  timeout->mRunning = false;

  return timeout->mCleared;
}

//*****************************************************************************
// nsGlobalWindowInner: Helper Functions
//*****************************************************************************

already_AddRefed<nsIDocShellTreeOwner>
nsGlobalWindowInner::GetTreeOwner()
{
  FORWARD_TO_OUTER(GetTreeOwner, (), nullptr);
}

already_AddRefed<nsIWebBrowserChrome>
nsGlobalWindowInner::GetWebBrowserChrome()
{
  nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();

  nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(treeOwner);
  return browserChrome.forget();
}

nsIScrollableFrame *
nsGlobalWindowInner::GetScrollFrame()
{
  FORWARD_TO_OUTER(GetScrollFrame, (), nullptr);
}

bool
nsGlobalWindowInner::IsPrivateBrowsing()
{
  nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(GetDocShell());
  return loadContext && loadContext->UsePrivateBrowsing();
}

void
nsGlobalWindowInner::FlushPendingNotifications(FlushType aType)
{
  if (mDoc) {
    mDoc->FlushPendingNotifications(aType);
  }
}

void
nsGlobalWindowInner::EnableDeviceSensor(uint32_t aType)
{
  bool alreadyEnabled = false;
  for (uint32_t i = 0; i < mEnabledSensors.Length(); i++) {
    if (mEnabledSensors[i] == aType) {
      alreadyEnabled = true;
      break;
    }
  }

  mEnabledSensors.AppendElement(aType);

  if (alreadyEnabled) {
    return;
  }

  nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
  if (ac) {
    ac->AddWindowListener(aType, this);
  }
}

void
nsGlobalWindowInner::DisableDeviceSensor(uint32_t aType)
{
  int32_t doomedElement = -1;
  int32_t listenerCount = 0;
  for (uint32_t i = 0; i < mEnabledSensors.Length(); i++) {
    if (mEnabledSensors[i] == aType) {
      doomedElement = i;
      listenerCount++;
    }
  }

  if (doomedElement == -1) {
    return;
  }

  mEnabledSensors.RemoveElementAt(doomedElement);

  if (listenerCount > 1) {
    return;
  }

  nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
  if (ac) {
    ac->RemoveWindowListener(aType, this);
  }
}

#if defined(MOZ_WIDGET_ANDROID)
void
nsGlobalWindowInner::EnableOrientationChangeListener()
{
  // XXX: mDocShell is never set on the inner window?
  nsIDocShell* docShell = nullptr;
  if (!nsContentUtils::ShouldResistFingerprinting(docShell) &&
      !mOrientationChangeObserver) {
    mOrientationChangeObserver =
      MakeUnique<WindowOrientationObserver>(this);
  }
}

void
nsGlobalWindowInner::DisableOrientationChangeListener()
{
  mOrientationChangeObserver = nullptr;
}
#endif

void
nsGlobalWindowInner::SetHasGamepadEventListener(bool aHasGamepad/* = true*/)
{
  mHasGamepad = aHasGamepad;
  if (aHasGamepad) {
    EnableGamepadUpdates();
  }
}


void
nsGlobalWindowInner::EventListenerAdded(nsAtom* aType)
{
  if (aType == nsGkAtoms::onvrdisplayactivate ||
      aType == nsGkAtoms::onvrdisplayconnect ||
      aType == nsGkAtoms::onvrdisplaydeactivate ||
      aType == nsGkAtoms::onvrdisplaydisconnect ||
      aType == nsGkAtoms::onvrdisplaypresentchange) {
    NotifyVREventListenerAdded();
  }

  if (aType == nsGkAtoms::onvrdisplayactivate) {
    mHasVRDisplayActivateEvents = true;
  }

  if (aType == nsGkAtoms::onbeforeunload &&
      mTabChild &&
      (!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS))) {
    mBeforeUnloadListenerCount++;
    MOZ_ASSERT(mBeforeUnloadListenerCount > 0);
    mTabChild->BeforeUnloadAdded();
  }

  // We need to initialize localStorage in order to receive notifications.
  if (aType == nsGkAtoms::onstorage) {
    ErrorResult rv;
    GetLocalStorage(rv);
    rv.SuppressException();
  }
}

void
nsGlobalWindowInner::EventListenerRemoved(nsAtom* aType)
{
  if (aType == nsGkAtoms::onbeforeunload &&
      mTabChild &&
      (!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS))) {
    mBeforeUnloadListenerCount--;
    MOZ_ASSERT(mBeforeUnloadListenerCount >= 0);
    mTabChild->BeforeUnloadRemoved();
  }
}

void
nsGlobalWindowInner::NotifyVREventListenerAdded()
{
  mHasVREvents = true;
  EnableVRUpdates();
}

bool
nsGlobalWindowInner::HasUsedVR() const
{
  // Returns true only if any WebVR API call or related event
  // has been used
  return mHasVREvents;
}

bool
nsGlobalWindowInner::IsVRContentDetected() const
{
  // Returns true only if the content will respond to
  // the VRDisplayActivate event.
  return mHasVRDisplayActivateEvents;
}

bool
nsGlobalWindowInner::IsVRContentPresenting() const
{
  for (const auto& display : mVRDisplays) {
    if (display->IsAnyPresenting(gfx::kVRGroupAll)) {
      return true;
    }
  }
  return false;
}

void
nsGlobalWindowInner::AddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const
{
  aWindowSizes.mDOMOtherSize += aWindowSizes.mState.mMallocSizeOf(this);
  aWindowSizes.mDOMOtherSize +=
    nsIGlobalObject::ShallowSizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);

  EventListenerManager* elm = GetExistingListenerManager();
  if (elm) {
    aWindowSizes.mDOMOtherSize +=
      elm->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
    aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
  }
  if (mDoc) {
    // Multiple global windows can share a document. So only measure the
    // document if it (a) doesn't have a global window, or (b) it's the
    // primary document for the window.
    if (!mDoc->GetInnerWindow() ||
        mDoc->GetInnerWindow() == this) {
      mDoc->DocAddSizeOfIncludingThis(aWindowSizes);
    }
  }

  if (mNavigator) {
    aWindowSizes.mDOMOtherSize +=
      mNavigator->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
  }

  ForEachEventTargetObject([&] (DOMEventTargetHelper* et, bool* aDoneOut) {
    if (nsCOMPtr<nsISizeOfEventTarget> iSizeOf = do_QueryObject(et)) {
      aWindowSizes.mDOMEventTargetsSize +=
        iSizeOf->SizeOfEventTargetIncludingThis(
          aWindowSizes.mState.mMallocSizeOf);
    }
    if (EventListenerManager* elm = et->GetExistingListenerManager()) {
      aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
    }
    ++aWindowSizes.mDOMEventTargetsCount;
  });

  if (mPerformance) {
    aWindowSizes.mDOMPerformanceUserEntries =
      mPerformance->SizeOfUserEntries(aWindowSizes.mState.mMallocSizeOf);
    aWindowSizes.mDOMPerformanceResourceEntries =
      mPerformance->SizeOfResourceEntries(aWindowSizes.mState.mMallocSizeOf);
  }

  aWindowSizes.mDOMOtherSize +=
    mPendingPromises.ShallowSizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
}

void
nsGlobalWindowInner::AddGamepad(uint32_t aIndex, Gamepad* aGamepad)
{
  // Create the index we will present to content based on which indices are
  // already taken, as required by the spec.
  // https://w3c.github.io/gamepad/gamepad.html#widl-Gamepad-index
  int index = 0;
  while(mGamepadIndexSet.Contains(index)) {
    ++index;
  }
  mGamepadIndexSet.Put(index);
  aGamepad->SetIndex(index);
  mGamepads.Put(aIndex, aGamepad);
}

void
nsGlobalWindowInner::RemoveGamepad(uint32_t aIndex)
{
  RefPtr<Gamepad> gamepad;
  if (!mGamepads.Get(aIndex, getter_AddRefs(gamepad))) {
    return;
  }
  // Free up the index we were using so it can be reused
  mGamepadIndexSet.Remove(gamepad->Index());
  mGamepads.Remove(aIndex);
}

void
nsGlobalWindowInner::GetGamepads(nsTArray<RefPtr<Gamepad> >& aGamepads)
{
  aGamepads.Clear();

  // navigator.getGamepads() always returns an empty array when
  // privacy.resistFingerprinting is true.
  if (nsContentUtils::ShouldResistFingerprinting()) {
    return;
  }

  // mGamepads.Count() may not be sufficient, but it's not harmful.
  aGamepads.SetCapacity(mGamepads.Count());
  for (auto iter = mGamepads.Iter(); !iter.Done(); iter.Next()) {
    Gamepad* gamepad = iter.UserData();
    aGamepads.EnsureLengthAtLeast(gamepad->Index() + 1);
    aGamepads[gamepad->Index()] = gamepad;
  }
}

already_AddRefed<Gamepad>
nsGlobalWindowInner::GetGamepad(uint32_t aIndex)
{
  RefPtr<Gamepad> gamepad;

  if (mGamepads.Get(aIndex, getter_AddRefs(gamepad))) {
    return gamepad.forget();
  }

  return nullptr;
}

void
nsGlobalWindowInner::SetHasSeenGamepadInput(bool aHasSeen)
{
  mHasSeenGamepadInput = aHasSeen;
}

bool
nsGlobalWindowInner::HasSeenGamepadInput()
{
  return mHasSeenGamepadInput;
}

void
nsGlobalWindowInner::SyncGamepadState()
{
  if (mHasSeenGamepadInput) {
    RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
    for (auto iter = mGamepads.Iter(); !iter.Done(); iter.Next()) {
      gamepadManager->SyncGamepadState(iter.Key(), iter.UserData());
    }
  }
}

void
nsGlobalWindowInner::StopGamepadHaptics()
{
  if (mHasSeenGamepadInput) {
    RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
    gamepadManager->StopHaptics();
  }
}

bool
nsGlobalWindowInner::UpdateVRDisplays(nsTArray<RefPtr<mozilla::dom::VRDisplay>>& aDevices)
{
  VRDisplay::UpdateVRDisplays(mVRDisplays, this);
  aDevices = mVRDisplays;
  return true;
}

void
nsGlobalWindowInner::NotifyActiveVRDisplaysChanged()
{
  if (mNavigator) {
    mNavigator->NotifyActiveVRDisplaysChanged();
  }
}

void
nsGlobalWindowInner::DispatchVRDisplayActivate(uint32_t aDisplayID,
                                               mozilla::dom::VRDisplayEventReason aReason)
{
  // Ensure that our list of displays is up to date
  VRDisplay::UpdateVRDisplays(mVRDisplays, this);

  // Search for the display identified with aDisplayID and fire the
  // event if found.
  for (const auto& display : mVRDisplays) {
    if (display->DisplayId() == aDisplayID) {
      if (aReason != VRDisplayEventReason::Navigation &&
          display->IsAnyPresenting(gfx::kVRGroupContent)) {
        // We only want to trigger this event if nobody is presenting to the
        // display already or when a page is loaded by navigating away
        // from a page with an active VR Presentation.
        continue;
      }

      VRDisplayEventInit init;
      init.mBubbles = false;
      init.mCancelable = false;
      init.mDisplay = display;
      init.mReason.Construct(aReason);

      RefPtr<VRDisplayEvent> event =
        VRDisplayEvent::Constructor(this,
                                    NS_LITERAL_STRING("vrdisplayactivate"),
                                    init);
      // vrdisplayactivate is a trusted event, allowing VRDisplay.requestPresent
      // to be used in response to link traversal, user request (chrome UX), and
      // HMD mounting detection sensors.
      event->SetTrusted(true);
      // VRDisplay.requestPresent normally requires a user gesture; however, an
      // exception is made to allow it to be called in response to vrdisplayactivate
      // during VR link traversal.
      display->StartHandlingVRNavigationEvent();
      DispatchEvent(*event);
      display->StopHandlingVRNavigationEvent();
      // Once we dispatch the event, we must not access any members as an event
      // listener can do anything, including closing windows.
      return;
    }
  }
}

void
nsGlobalWindowInner::DispatchVRDisplayDeactivate(uint32_t aDisplayID,
                                                 mozilla::dom::VRDisplayEventReason aReason)
{
  // Ensure that our list of displays is up to date
  VRDisplay::UpdateVRDisplays(mVRDisplays, this);

  // Search for the display identified with aDisplayID and fire the
  // event if found.
  for (const auto& display : mVRDisplays) {
    if (display->DisplayId() == aDisplayID && display->IsPresenting()) {
      // We only want to trigger this event to content that is presenting to
      // the display already.

      VRDisplayEventInit init;
      init.mBubbles = false;
      init.mCancelable = false;
      init.mDisplay = display;
      init.mReason.Construct(aReason);

      RefPtr<VRDisplayEvent> event =
        VRDisplayEvent::Constructor(this,
                                    NS_LITERAL_STRING("vrdisplaydeactivate"),
                                    init);
      event->SetTrusted(true);
      DispatchEvent(*event);
      // Once we dispatch the event, we must not access any members as an event
      // listener can do anything, including closing windows.
      return;
    }
  }
}

void
nsGlobalWindowInner::DispatchVRDisplayConnect(uint32_t aDisplayID)
{
  // Ensure that our list of displays is up to date
  VRDisplay::UpdateVRDisplays(mVRDisplays, this);

  // Search for the display identified with aDisplayID and fire the
  // event if found.
  for (const auto& display : mVRDisplays) {
    if (display->DisplayId() == aDisplayID) {
      // Fire event even if not presenting to the display.
      VRDisplayEventInit init;
      init.mBubbles = false;
      init.mCancelable = false;
      init.mDisplay = display;
      // VRDisplayEvent.reason is not set for vrdisplayconnect

      RefPtr<VRDisplayEvent> event =
        VRDisplayEvent::Constructor(this,
                                    NS_LITERAL_STRING("vrdisplayconnect"),
                                    init);
      event->SetTrusted(true);
      DispatchEvent(*event);
      // Once we dispatch the event, we must not access any members as an event
      // listener can do anything, including closing windows.
      return;
    }
  }
}

void
nsGlobalWindowInner::DispatchVRDisplayDisconnect(uint32_t aDisplayID)
{
  // Ensure that our list of displays is up to date
  VRDisplay::UpdateVRDisplays(mVRDisplays, this);

  // Search for the display identified with aDisplayID and fire the
  // event if found.
  for (const auto& display : mVRDisplays) {
    if (display->DisplayId() == aDisplayID) {
      // Fire event even if not presenting to the display.
      VRDisplayEventInit init;
      init.mBubbles = false;
      init.mCancelable = false;
      init.mDisplay = display;
      // VRDisplayEvent.reason is not set for vrdisplaydisconnect

      RefPtr<VRDisplayEvent> event =
        VRDisplayEvent::Constructor(this,
                                    NS_LITERAL_STRING("vrdisplaydisconnect"),
                                    init);
      event->SetTrusted(true);
      DispatchEvent(*event);
      // Once we dispatch the event, we must not access any members as an event
      // listener can do anything, including closing windows.
      return;
    }
  }
}

void
nsGlobalWindowInner::DispatchVRDisplayPresentChange(uint32_t aDisplayID)
{
  // Ensure that our list of displays is up to date
  VRDisplay::UpdateVRDisplays(mVRDisplays, this);

  // Search for the display identified with aDisplayID and fire the
  // event if found.
  for (const auto& display : mVRDisplays) {
    if (display->DisplayId() == aDisplayID) {
      // Fire event even if not presenting to the display.
      VRDisplayEventInit init;
      init.mBubbles = false;
      init.mCancelable = false;
      init.mDisplay = display;
      // VRDisplayEvent.reason is not set for vrdisplaypresentchange
      RefPtr<VRDisplayEvent> event =
        VRDisplayEvent::Constructor(this,
                                    NS_LITERAL_STRING("vrdisplaypresentchange"),
                                    init);
      event->SetTrusted(true);
      DispatchEvent(*event);
      // Once we dispatch the event, we must not access any members as an event
      // listener can do anything, including closing windows.
      return;
    }
  }
}

enum WindowState {
  // These constants need to match the constants in Window.webidl
  STATE_MAXIMIZED = 1,
  STATE_MINIMIZED = 2,
  STATE_NORMAL = 3,
  STATE_FULLSCREEN = 4
};

uint16_t
nsGlobalWindowInner::WindowState()
{
  nsCOMPtr<nsIWidget> widget = GetMainWidget();

  int32_t mode = widget ? widget->SizeMode() : 0;

  switch (mode) {
    case nsSizeMode_Minimized:
      return STATE_MINIMIZED;
    case nsSizeMode_Maximized:
      return STATE_MAXIMIZED;
    case nsSizeMode_Fullscreen:
      return STATE_FULLSCREEN;
    case nsSizeMode_Normal:
      return STATE_NORMAL;
    default:
      NS_WARNING("Illegal window state for this chrome window");
      break;
  }

  return STATE_NORMAL;
}

bool
nsGlobalWindowInner::IsFullyOccluded()
{
  nsCOMPtr<nsIWidget> widget = GetMainWidget();
  return widget && widget->IsFullyOccluded();
}

void
nsGlobalWindowInner::Maximize()
{
  nsCOMPtr<nsIWidget> widget = GetMainWidget();

  if (widget) {
    widget->SetSizeMode(nsSizeMode_Maximized);
  }
}

void
nsGlobalWindowInner::Minimize()
{
  nsCOMPtr<nsIWidget> widget = GetMainWidget();

  if (widget) {
    widget->SetSizeMode(nsSizeMode_Minimized);
  }
}

void
nsGlobalWindowInner::Restore()
{
  nsCOMPtr<nsIWidget> widget = GetMainWidget();

  if (widget) {
    widget->SetSizeMode(nsSizeMode_Normal);
  }
}

void
nsGlobalWindowInner::GetAttention(ErrorResult& aResult)
{
  return GetAttentionWithCycleCount(-1, aResult);
}

void
nsGlobalWindowInner::GetAttentionWithCycleCount(int32_t aCycleCount,
                                                ErrorResult& aError)
{
  nsCOMPtr<nsIWidget> widget = GetMainWidget();

  if (widget) {
    aError = widget->GetAttention(aCycleCount);
  }
}

void
nsGlobalWindowInner::BeginWindowMove(Event& aMouseDownEvent,
                                     ErrorResult& aError)
{
  nsCOMPtr<nsIWidget> widget = GetMainWidget();

  if (!widget) {
    return;
  }

  WidgetMouseEvent* mouseEvent =
    aMouseDownEvent.WidgetEventPtr()->AsMouseEvent();
  if (!mouseEvent || mouseEvent->mClass != eMouseEventClass) {
    aError.Throw(NS_ERROR_FAILURE);
    return;
  }

  aError = widget->BeginMoveDrag(mouseEvent);
}

already_AddRefed<Promise>
nsGlobalWindowInner::PromiseDocumentFlushed(PromiseDocumentFlushedCallback& aCallback,
                                            ErrorResult& aError)
{
  MOZ_RELEASE_ASSERT(IsChromeWindow());

  if (!IsCurrentInnerWindow()) {
    aError.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  if (mIteratingDocumentFlushedResolvers) {
    aError.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  if (!mDoc) {
    aError.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  nsIPresShell* shell = mDoc->GetShell();
  if (!shell) {
    aError.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  // We need to associate the lifetime of the Promise to the lifetime
  // of the caller's global. That way, if the window we're observing
  // refresh driver ticks on goes away before our observer is fired,
  // we can still resolve the Promise.
  nsIGlobalObject* global = GetIncumbentGlobal();
  if (!global) {
    aError.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  RefPtr<Promise> resultPromise = Promise::Create(global, aError);
  if (aError.Failed()) {
    return nullptr;
  }

  UniquePtr<PromiseDocumentFlushedResolver> flushResolver(
    new PromiseDocumentFlushedResolver(resultPromise, aCallback));

  if (!shell->NeedStyleFlush() && !shell->NeedLayoutFlush()) {
    flushResolver->Call();
    return resultPromise.forget();
  }

  if (!mObservingDidRefresh) {
    bool success = shell->AddPostRefreshObserver(this);
    if (!success) {
      aError.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }
    mObservingDidRefresh = true;
  }

  mDocumentFlushedResolvers.AppendElement(std::move(flushResolver));
  return resultPromise.forget();
}

template<bool call>
void
nsGlobalWindowInner::CallOrCancelDocumentFlushedResolvers()
{
  MOZ_ASSERT(!mIteratingDocumentFlushedResolvers);

  while (true) {
    {
      // To coalesce MicroTask checkpoints inside callback call, enclose the
      // inner loop with nsAutoMicroTask, and perform a MicroTask checkpoint
      // after the loop.
      nsAutoMicroTask mt;

      mIteratingDocumentFlushedResolvers = true;
      for (const auto& documentFlushedResolver : mDocumentFlushedResolvers) {
        if (call) {
          documentFlushedResolver->Call();
        } else {
          documentFlushedResolver->Cancel();
        }
      }
      mDocumentFlushedResolvers.Clear();
      mIteratingDocumentFlushedResolvers = false;
    }

    // Leaving nsAutoMicroTask above will perform MicroTask checkpoint, and
    // Promise callbacks there may create mDocumentFlushedResolvers items.

    // If there's no new item, there's nothing to do here.
    if (!mDocumentFlushedResolvers.Length()) {
      break;
    }

    // If there are new items, the observer is not added for them when calling
    // PromiseDocumentFlushed.  Add here and leave.
    // FIXME: Handle this case inside PromiseDocumentFlushed (bug 1442824).
    if (mDoc) {
      nsIPresShell* shell = mDoc->GetShell();
      if (shell) {
        (void) shell->AddPostRefreshObserver(this);
        break;
      }
    }

    // If we fail adding observer, keep looping to resolve or reject all
    // promises.  This case happens while destroying window.
    // This violates the constraint that the promiseDocumentFlushed callback
    // only ever run when no flush needed, but it's necessary to resolve
    // Promise returned by that.
  }
}

void
nsGlobalWindowInner::CallDocumentFlushedResolvers()
{
  CallOrCancelDocumentFlushedResolvers<true>();
}

void
nsGlobalWindowInner::CancelDocumentFlushedResolvers()
{
  CallOrCancelDocumentFlushedResolvers<false>();
}

void
nsGlobalWindowInner::DidRefresh()
{
  auto rejectionGuard = MakeScopeExit([&] {
    CancelDocumentFlushedResolvers();
    mObservingDidRefresh = false;
  });

  MOZ_ASSERT(mDoc);

  nsIPresShell* shell = mDoc->GetShell();
  MOZ_ASSERT(shell);

  if (shell->NeedStyleFlush() || shell->NeedLayoutFlush()) {
    // By the time our observer fired, something has already invalidated
    // style or layout - or perhaps we're still in the middle of a flush that
    // was interrupted. In either case, we'll wait until the next refresh driver
    // tick instead and try again.
    rejectionGuard.release();
    return;
  }

  bool success = shell->RemovePostRefreshObserver(this);
  if (!success) {
    return;
  }

  rejectionGuard.release();

  CallDocumentFlushedResolvers();
  mObservingDidRefresh = false;
}

already_AddRefed<nsWindowRoot>
nsGlobalWindowInner::GetWindowRoot(mozilla::ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetWindowRootOuter, (), aError, nullptr);
}

void
nsGlobalWindowInner::SetCursor(const nsAString& aCursor, ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetCursorOuter, (aCursor, aError), aError, );
}

NS_IMETHODIMP
nsGlobalWindowInner::GetBrowserDOMWindow(nsIBrowserDOMWindow **aBrowserWindow)
{
  MOZ_RELEASE_ASSERT(IsChromeWindow());

  ErrorResult rv;
  NS_IF_ADDREF(*aBrowserWindow = GetBrowserDOMWindow(rv));
  return rv.StealNSResult();
}

nsIBrowserDOMWindow*
nsGlobalWindowInner::GetBrowserDOMWindow(ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(GetBrowserDOMWindowOuter, (), aError, nullptr);
}

void
nsGlobalWindowInner::SetBrowserDOMWindow(nsIBrowserDOMWindow* aBrowserWindow,
                                    ErrorResult& aError)
{
  FORWARD_TO_OUTER_OR_THROW(SetBrowserDOMWindowOuter, (aBrowserWindow), aError, );
}

void
nsGlobalWindowInner::NotifyDefaultButtonLoaded(Element& aDefaultButton,
                                               ErrorResult& aError)
{
#ifdef MOZ_XUL
  // Don't snap to a disabled button.
  nsCOMPtr<nsIDOMXULControlElement> xulControl =
                                      do_QueryInterface(&aDefaultButton);
  if (!xulControl) {
    aError.Throw(NS_ERROR_FAILURE);
    return;
  }
  bool disabled;
  aError = xulControl->GetDisabled(&disabled);
  if (aError.Failed() || disabled) {
    return;
  }

  // Get the button rect in screen coordinates.
  nsIFrame *frame = aDefaultButton.GetPrimaryFrame();
  if (!frame) {
    aError.Throw(NS_ERROR_FAILURE);
    return;
  }
  LayoutDeviceIntRect buttonRect =
    LayoutDeviceIntRect::FromAppUnitsToNearest(
      frame->GetScreenRectInAppUnits(),
      frame->PresContext()->AppUnitsPerDevPixel());

  // Get the widget rect in screen coordinates.
  nsIWidget *widget = GetNearestWidget();
  if (!widget) {
    aError.Throw(NS_ERROR_FAILURE);
    return;
  }
  LayoutDeviceIntRect widgetRect = widget->GetScreenBounds();

  // Convert the buttonRect coordinates from screen to the widget.
  buttonRect -= widgetRect.TopLeft();
  nsresult rv = widget->OnDefaultButtonLoaded(buttonRect);
  if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
    aError.Throw(rv);
  }
#else
  aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
#endif
}

ChromeMessageBroadcaster*
nsGlobalWindowInner::MessageManager()
{
  MOZ_ASSERT(IsChromeWindow());
  if (!mChromeFields.mMessageManager) {
    RefPtr<ChromeMessageBroadcaster> globalMM =
      nsFrameMessageManager::GetGlobalMessageManager();
    mChromeFields.mMessageManager = new ChromeMessageBroadcaster(globalMM);
  }
  return mChromeFields.mMessageManager;
}

ChromeMessageBroadcaster*
nsGlobalWindowInner::GetGroupMessageManager(const nsAString& aGroup)
{
  MOZ_ASSERT(IsChromeWindow());

  RefPtr<ChromeMessageBroadcaster> messageManager =
    mChromeFields.mGroupMessageManagers.LookupForAdd(aGroup).OrInsert(
      [this] () {
        return new ChromeMessageBroadcaster(MessageManager());
      });
  return messageManager;
}

void
nsGlobalWindowInner::InitWasOffline()
{
  mWasOffline = NS_IsOffline();
}

#if defined(MOZ_WIDGET_ANDROID)
int16_t
nsGlobalWindowInner::Orientation(CallerType aCallerType) const
{
  return nsContentUtils::ResistFingerprinting(aCallerType) ?
           0 : WindowOrientationObserver::OrientationAngle();
}
#endif

already_AddRefed<Console>
nsGlobalWindowInner::GetConsole(JSContext* aCx, ErrorResult& aRv)
{
  if (!mConsole) {
    mConsole = Console::Create(aCx, this, aRv);
    if (NS_WARN_IF(aRv.Failed())) {
      return nullptr;
    }
  }

  RefPtr<Console> console = mConsole;
  return console.forget();
}

bool
nsGlobalWindowInner::IsSecureContext() const
{
  JS::Realm* realm = js::GetNonCCWObjectRealm(GetWrapperPreserveColor());
  return JS::GetIsSecureContext(realm);
}

already_AddRefed<External>
nsGlobalWindowInner::GetExternal(ErrorResult& aRv)
{
#ifdef HAVE_SIDEBAR
  if (!mExternal) {
    JS::Rooted<JSObject*> jsImplObj(RootingCx());
    ConstructJSImplementation("@mozilla.org/sidebar;1", this, &jsImplObj, aRv);
    if (aRv.Failed()) {
      return nullptr;
    }
    mExternal = new External(jsImplObj, this);
  }

  RefPtr<External> external = static_cast<External*>(mExternal.get());
  return external.forget();
#else
  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
  return nullptr;
#endif
}

void
nsGlobalWindowInner::GetSidebar(OwningExternalOrWindowProxy& aResult,
                                ErrorResult& aRv)
{
#ifdef HAVE_SIDEBAR
  // First check for a named frame named "sidebar"
  nsCOMPtr<nsPIDOMWindowOuter> domWindow = GetChildWindow(NS_LITERAL_STRING("sidebar"));
  if (domWindow) {
    aResult.SetAsWindowProxy() = domWindow.forget();
    return;
  }

  RefPtr<External> external = GetExternal(aRv);
  if (external) {
    aResult.SetAsExternal() = external;
  }
#else
  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
#endif
}

void
nsGlobalWindowInner::ClearDocumentDependentSlots(JSContext* aCx)
{
  // If JSAPI OOMs here, there is basically nothing we can do to recover safely.
  if (!WindowBinding::ClearCachedDocumentValue(aCx, this) ||
      !WindowBinding::ClearCachedPerformanceValue(aCx, this)) {
    MOZ_CRASH("Unhandlable OOM while clearing document dependent slots.");
  }
}

/* static */
JSObject*
nsGlobalWindowInner::CreateNamedPropertiesObject(JSContext *aCx,
                                                 JS::Handle<JSObject*> aProto)
{
  return WindowNamedPropertiesHandler::Create(aCx, aProto);
}

void
nsGlobalWindowInner::RedefineProperty(JSContext* aCx, const char* aPropName,
                                      JS::Handle<JS::Value> aValue,
                                      ErrorResult& aError)
{
  JS::Rooted<JSObject*> thisObj(aCx, GetWrapperPreserveColor());
  if (!thisObj) {
    aError.Throw(NS_ERROR_UNEXPECTED);
    return;
  }

  if (!JS_WrapObject(aCx, &thisObj) ||
      !JS_DefineProperty(aCx, thisObj, aPropName, aValue, JSPROP_ENUMERATE)) {
    aError.Throw(NS_ERROR_FAILURE);
  }
}

void
nsGlobalWindowInner::GetReplaceableWindowCoord(JSContext* aCx,
                                               nsGlobalWindowInner::WindowCoordGetter aGetter,
                                               JS::MutableHandle<JS::Value> aRetval,
                                               CallerType aCallerType,
                                               ErrorResult& aError)
{
  int32_t coord = (this->*aGetter)(aCallerType, aError);
  if (!aError.Failed() &&
      !ToJSValue(aCx, coord, aRetval)) {
    aError.Throw(NS_ERROR_FAILURE);
  }
}

void
nsGlobalWindowInner::SetReplaceableWindowCoord(JSContext* aCx,
                                               nsGlobalWindowInner::WindowCoordSetter aSetter,
                                               JS::Handle<JS::Value> aValue,
                                               const char* aPropName,
                                               CallerType aCallerType,
                                               ErrorResult& aError)
{
  /*
   * If caller is not chrome and the user has not explicitly exempted the site,
   * just treat this the way we would an IDL replaceable property.
   */
  nsGlobalWindowOuter* outer = GetOuterWindowInternal();
  if (!outer ||
      !outer->CanMoveResizeWindows(aCallerType) ||
      outer->IsFrame()) {
    RedefineProperty(aCx, aPropName, aValue, aError);
    return;
  }

  int32_t value;
  if (!ValueToPrimitive<int32_t, eDefault>(aCx, aValue, &value)) {
    aError.Throw(NS_ERROR_UNEXPECTED);
    return;
  }

  if (nsContentUtils::ShouldResistFingerprinting(GetDocShell())) {
    bool innerWidthSpecified = false;
    bool innerHeightSpecified = false;
    bool outerWidthSpecified = false;
    bool outerHeightSpecified = false;

    if (strcmp(aPropName, "innerWidth") == 0) {
      innerWidthSpecified = true;
    } else if (strcmp(aPropName, "innerHeight") == 0) {
      innerHeightSpecified = true;
    } else if (strcmp(aPropName, "outerWidth") == 0) {
      outerWidthSpecified = true;
    } else if (strcmp(aPropName, "outerHeight") == 0) {
      outerHeightSpecified = true;
    }

    if (innerWidthSpecified || innerHeightSpecified ||
        outerWidthSpecified || outerHeightSpecified)
    {
      nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = outer->GetTreeOwnerWindow();
      nsCOMPtr<nsIScreen> screen;
      nsCOMPtr<nsIScreenManager> screenMgr(
        do_GetService("@mozilla.org/gfx/screenmanager;1"));
      int32_t winLeft   = 0;
      int32_t winTop    = 0;
      int32_t winWidth  = 0;
      int32_t winHeight = 0;
      double scale = 1.0;


      if (treeOwnerAsWin && screenMgr) {
        // Acquire current window size.
        treeOwnerAsWin->GetUnscaledDevicePixelsPerCSSPixel(&scale);
        treeOwnerAsWin->GetPositionAndSize(&winLeft, &winTop, &winWidth, &winHeight);
        winLeft = NSToIntRound(winHeight / scale);
        winTop = NSToIntRound(winWidth / scale);
        winWidth = NSToIntRound(winWidth / scale);
        winHeight = NSToIntRound(winHeight / scale);

        // Acquire content window size.
        CSSIntSize contentSize;
        outer->GetInnerSize(contentSize);

        screenMgr->ScreenForRect(winLeft, winTop, winWidth, winHeight,
                                 getter_AddRefs(screen));

        if (screen) {
          int32_t* targetContentWidth  = nullptr;
          int32_t* targetContentHeight = nullptr;
          int32_t screenWidth  = 0;
          int32_t screenHeight = 0;
          int32_t chromeWidth  = 0;
          int32_t chromeHeight = 0;
          int32_t inputWidth   = 0;
          int32_t inputHeight  = 0;
          int32_t unused = 0;

          // Get screen dimensions (in device pixels)
          screen->GetAvailRect(&unused, &unused, &screenWidth,
                               &screenHeight);
          // Convert them to CSS pixels
          screenWidth = NSToIntRound(screenWidth / scale);
          screenHeight = NSToIntRound(screenHeight / scale);

          // Calculate the chrome UI size.
          chromeWidth = winWidth - contentSize.width;
          chromeHeight = winHeight - contentSize.height;

          if (innerWidthSpecified || outerWidthSpecified) {
            inputWidth = value;
            targetContentWidth = &value;
            targetContentHeight = &unused;
          } else if (innerHeightSpecified || outerHeightSpecified) {
            inputHeight = value;
            targetContentWidth = &unused;
            targetContentHeight = &value;
          }

          nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting(
            chromeWidth,
            chromeHeight,
            screenWidth,
            screenHeight,
            inputWidth,
            inputHeight,
            outerWidthSpecified,
            outerHeightSpecified,
            targetContentWidth,
            targetContentHeight
          );
        }
      }
    }
  }

  (this->*aSetter)(value, aCallerType, aError);
}

void
nsGlobalWindowInner::FireOnNewGlobalObject()
{
  // AutoEntryScript required to invoke debugger hook, which is a
  // Gecko-specific concept at present.
  AutoEntryScript aes(this, "nsGlobalWindowInner report new global");
  JS::Rooted<JSObject*> global(aes.cx(), GetWrapper());
  JS_FireOnNewGlobalObject(aes.cx(), global);
}

#ifdef _WINDOWS_
#error "Never include windows.h in this file!"
#endif

already_AddRefed<Promise>
nsGlobalWindowInner::CreateImageBitmap(JSContext* aCx,
                                       const ImageBitmapSource& aImage,
                                       ErrorResult& aRv)
{
  if (aImage.IsArrayBuffer() || aImage.IsArrayBufferView()) {
    aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
    return nullptr;
  }

  return ImageBitmap::Create(this, aImage, Nothing(), aRv);
}

already_AddRefed<Promise>
nsGlobalWindowInner::CreateImageBitmap(JSContext* aCx,
                                       const ImageBitmapSource& aImage,
                                       int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh,
                                       ErrorResult& aRv)
{
  if (aImage.IsArrayBuffer() || aImage.IsArrayBufferView()) {
    aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
    return nullptr;
  }

  return ImageBitmap::Create(this, aImage, Some(gfx::IntRect(aSx, aSy, aSw, aSh)), aRv);
}

already_AddRefed<mozilla::dom::Promise>
nsGlobalWindowInner::CreateImageBitmap(JSContext* aCx,
                                       const ImageBitmapSource& aImage,
                                       int32_t aOffset, int32_t aLength,
                                       ImageBitmapFormat aFormat,
                                       const Sequence<ChannelPixelLayout>& aLayout,
                                       ErrorResult& aRv)
{
  if (!DOMPrefs::ImageBitmapExtensionsEnabled()) {
    aRv.Throw(NS_ERROR_TYPE_ERR);
    return nullptr;
  }
  if (aImage.IsArrayBuffer() || aImage.IsArrayBufferView()) {
    return ImageBitmap::Create(this, aImage, aOffset, aLength, aFormat, aLayout,
                               aRv);
  }
  aRv.Throw(NS_ERROR_TYPE_ERR);
  return nullptr;
}

mozilla::dom::TabGroup*
nsGlobalWindowInner::TabGroupInner()
{
  // If we don't have a TabGroup yet, try to get it from the outer window and
  // cache it.
  if (!mTabGroup) {
    nsGlobalWindowOuter* outer = GetOuterWindowInternal();
    // This will never be called without either an outer window, or a cached tab group.
    // This is because of the following:
    // * This method is only called on inner windows
    // * This method is called as a document is attached to it's script global
    //   by the document
    // * Inner windows are created in nsGlobalWindowInner::SetNewDocument, which
    //   immediately sets a document, which will call this method, causing
    //   the TabGroup to be cached.
    MOZ_RELEASE_ASSERT(outer, "Inner window without outer window has no cached tab group!");
    mTabGroup = outer->TabGroup();
  }
  MOZ_ASSERT(mTabGroup);

#ifdef DEBUG
  nsGlobalWindowOuter* outer = GetOuterWindowInternal();
  MOZ_ASSERT_IF(outer, outer->TabGroup() == mTabGroup);
#endif

  return mTabGroup;
}

nsresult
nsGlobalWindowInner::Dispatch(TaskCategory aCategory,
                              already_AddRefed<nsIRunnable>&& aRunnable)
{
  MOZ_RELEASE_ASSERT(NS_IsMainThread());
  if (GetDocGroup()) {
    return GetDocGroup()->Dispatch(aCategory, std::move(aRunnable));
  }
  return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable));
}

nsISerialEventTarget*
nsGlobalWindowInner::EventTargetFor(TaskCategory aCategory) const
{
  MOZ_RELEASE_ASSERT(NS_IsMainThread());
  if (GetDocGroup()) {
    return GetDocGroup()->EventTargetFor(aCategory);
  }
  return DispatcherTrait::EventTargetFor(aCategory);
}

AbstractThread*
nsGlobalWindowInner::AbstractMainThreadFor(TaskCategory aCategory)
{
  MOZ_RELEASE_ASSERT(NS_IsMainThread());
  if (GetDocGroup()) {
    return GetDocGroup()->AbstractMainThreadFor(aCategory);
  }
  return DispatcherTrait::AbstractMainThreadFor(aCategory);
}

Worklet*
nsGlobalWindowInner::GetAudioWorklet(ErrorResult& aRv)
{
  if (!mAudioWorklet) {
    nsIPrincipal* principal = GetPrincipal();
    if (!principal) {
      aRv.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }

    mAudioWorklet = new Worklet(this, principal, Worklet::eAudioWorklet);
  }

  return mAudioWorklet;
}

Worklet*
nsGlobalWindowInner::GetPaintWorklet(ErrorResult& aRv)
{
  if (!mPaintWorklet) {
    nsIPrincipal* principal = GetPrincipal();
    if (!principal) {
      aRv.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }

    mPaintWorklet = new Worklet(this, principal, Worklet::ePaintWorklet);
  }

  return mPaintWorklet;
}

void
nsGlobalWindowInner::GetRegionalPrefsLocales(nsTArray<nsString>& aLocales)
{
  AutoTArray<nsCString, 10> rpLocales;
  mozilla::intl::LocaleService::GetInstance()->GetRegionalPrefsLocales(rpLocales);

  for (const auto& loc : rpLocales) {
    aLocales.AppendElement(NS_ConvertUTF8toUTF16(loc));
  }
}

IntlUtils*
nsGlobalWindowInner::GetIntlUtils(ErrorResult& aError)
{
  if (!mIntlUtils) {
    mIntlUtils = new IntlUtils(this);
  }

  return mIntlUtils;
}

mozilla::dom::TabGroup*
nsPIDOMWindowInner::TabGroup()
{
  return nsGlobalWindowInner::Cast(this)->TabGroupInner();
}

/* static */ already_AddRefed<nsGlobalWindowInner>
nsGlobalWindowInner::Create(nsGlobalWindowOuter *aOuterWindow, bool aIsChrome)
{
  RefPtr<nsGlobalWindowInner> window = new nsGlobalWindowInner(aOuterWindow);
  if (aIsChrome) {
    window->mIsChrome = true;
    window->mCleanMessageManager = true;
  }

  window->InitWasOffline();
  return window.forget();
}

nsIURI*
nsPIDOMWindowInner::GetDocumentURI() const
{
  return mDoc ? mDoc->GetDocumentURI() : mDocumentURI.get();
}

nsIURI*
nsPIDOMWindowInner::GetDocBaseURI() const
{
  return mDoc ? mDoc->GetDocBaseURI() : mDocBaseURI.get();
}

void
nsPIDOMWindowInner::MaybeCreateDoc()
{
  // XXX: Forward to outer?
  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
nsGlobalWindowInner::PropagateClearSiteDataReload(const nsACString& aOrigin)
{
  nsIPrincipal* principal = GetPrincipal();
  if (!principal) {
    return;
  }

  nsAutoCString origin;
  nsresult rv = principal->GetOrigin(origin);
  NS_ENSURE_SUCCESS_VOID(rv);

  // If the URL of this window matches, let's refresh this window only.
  // We don't need to traverse the DOM tree.
  if (origin.Equals(aOrigin)) {
    nsCOMPtr<nsIDocShell> docShell = GetDocShell();
    nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(docShell));
    if (NS_WARN_IF(!webNav)) {
      return;
    }

    // We don't need any special reload flags, because this notification is
    // dispatched by Clear-Site-Data header, which should have already cleaned
    // up all the needed data.
    rv = webNav->Reload(nsIWebNavigation::LOAD_FLAGS_NONE);
    NS_ENSURE_SUCCESS_VOID(rv);

    return;
  }

  CallOnChildren(&nsGlobalWindowInner::PropagateClearSiteDataReload, aOrigin);
}

mozilla::dom::DocGroup*
nsPIDOMWindowInner::GetDocGroup() const
{
  nsIDocument* doc = GetExtantDoc();
  if (doc) {
    return doc->GetDocGroup();
  }
  return nullptr;
}

nsIGlobalObject*
nsPIDOMWindowInner::AsGlobal()
{
  return nsGlobalWindowInner::Cast(this);
}

const nsIGlobalObject*
nsPIDOMWindowInner::AsGlobal() const
{
  return nsGlobalWindowInner::Cast(this);
}

// XXX: Can we define this in a header instead of here?
namespace mozilla {
namespace dom {
extern uint64_t
NextWindowID();
} // namespace dom
} // namespace mozilla

nsPIDOMWindowInner::nsPIDOMWindowInner(nsPIDOMWindowOuter *aOuterWindow)
: mMutationBits(0), mActivePeerConnections(0), mIsDocumentLoaded(false),
  mIsHandlingResizeEvent(false),
  mMayHavePaintEventListener(false), mMayHaveTouchEventListener(false),
  mMayHaveSelectionChangeEventListener(false),
  mMayHaveMouseEnterLeaveEventListener(false),
  mMayHavePointerEnterLeaveEventListener(false),
  mAudioCaptured(false),
  mOuterWindow(aOuterWindow),
  // Make sure no actual window ends up with mWindowID == 0
  mWindowID(NextWindowID()), mHasNotifiedGlobalCreated(false),
  mMarkedCCGeneration(0),
  mHasTriedToCacheTopInnerWindow(false),
  mNumOfIndexedDBDatabases(0),
  mNumOfOpenWebSockets(0)
{
  MOZ_ASSERT(aOuterWindow);
}

nsPIDOMWindowInner::~nsPIDOMWindowInner() {}

#undef FORWARD_TO_OUTER
#undef FORWARD_TO_OUTER_OR_THROW
#undef FORWARD_TO_OUTER_VOID
