dom/base/nsGlobalWindowOuter.cpp
author Boris Zbarsky <bzbarsky@mit.edu>
Wed, 06 Feb 2019 13:01:27 +0000
changeset 457447 b7646b8ccfd38b1ef1b61d3170d3223b05628b80
parent 457292 00e93266060913ec9fe366e8fb335a3a9430524a
child 457476 d5850db1fb0dcee10fa18195594010a3f1e662b1
permissions -rw-r--r--
Bug 1525355. Make sure to clear out the cached-function-map slot on WindowProxy before we transplant it. r=peterv Differential Revision: https://phabricator.services.mozilla.com/D18725

/* -*- 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 "nsICookieService.h"
#include "nsIDOMStorageManager.h"
#include "nsIPermission.h"
#include "nsIPermissionManager.h"
#include "nsIPrefBranch.h"
#include "nsISecureBrowserUI.h"
#include "nsIWebProgressListener.h"
#include "mozilla/AntiTrackingCommon.h"
#include "mozilla/dom/ContentFrameMessageManager.h"
#include "mozilla/dom/EventTarget.h"
#include "mozilla/dom/LocalStorage.h"
#include "mozilla/dom/LSObject.h"
#include "mozilla/dom/Storage.h"
#include "mozilla/dom/IdleRequest.h"
#include "mozilla/dom/MaybeCrossOriginObject.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/dom/WindowProxyHolder.h"
#include "mozilla/IntegerPrintfMacros.h"
#if defined(MOZ_WIDGET_ANDROID)
#  include "mozilla/dom/WindowOrientationObserver.h"
#endif
#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 "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"
#include "nsDocShellLoadState.h"
#include "mozilla/dom/WindowGlobalChild.h"

// Helper Classes
#include "nsJSUtils.h"
#include "jsapi.h"
#include "js/PropertySpec.h"
#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/Components.h"
#include "mozilla/Debug.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStates.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/ProcessHangMonitor.h"
#include "mozilla/StaticPrefs.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 "mozilla/dom/Document.h"
#include "Crypto.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 "nsComputedDOMStyle.h"
#include "nsDOMCID.h"
#include "nsDOMWindowUtils.h"
#include "nsIWindowWatcher.h"
#include "nsPIWindowWatcher.h"
#include "nsIContentViewer.h"
#include "nsIScriptError.h"
#include "nsIControllers.h"
#include "nsIControllerContext.h"
#include "nsGlobalWindowCommands.h"
#include "nsQueryObject.h"
#include "nsContentUtils.h"
#include "nsCSSProps.h"
#include "nsIURIFixup.h"
#include "nsIURIMutator.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventStateManager.h"
#include "nsIObserverService.h"
#include "nsFocusManager.h"
#include "nsIXULWindow.h"
#include "nsITimedChannel.h"
#include "nsServiceManagerUtils.h"
#include "mozilla/dom/CustomEvent.h"
#include "nsIJARChannel.h"
#include "nsIScreenManager.h"
#include "nsIEffectiveTLDService.h"

#include "xpcprivate.h"

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

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

#include "XULDocument.h"
#include "nsIDOMXULCommandDispatcher.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 "nsBaseCommandController.h"
#include "nsXULControllers.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/ServiceWorkerRegistration.h"
#include "mozilla/dom/U2F.h"
#include "mozilla/dom/WebIDLGlobalNameHash.h"
#include "mozilla/dom/Worklet.h"
#ifdef HAVE_SIDEBAR
#  include "mozilla/dom/ExternalBinding.h"
#endif

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

// 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::BasePrincipal;
using mozilla::OriginAttributes;
using mozilla::TimeStamp;

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

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

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

static LazyLogModule gDOMLeakPRLogOuter("DOMLeakOuter");

static int32_t gOpenPopupSpamCount = 0;

static bool gSyncContentBlockingNotifications = false;

nsGlobalWindowOuter::OuterWindowByIdTable*
    nsGlobalWindowOuter::sOuterWindowsById = nullptr;

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

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

  return outer;
}

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

// Give OuterWindowProxyClass 2 reserved slots, like the other wrappers, so
// JSObject::swap can swap it with CrossCompartmentWrappers without requiring
// malloc.
//
// We store the nsGlobalWindowOuter* in our first slot.
//
// We store our holder weakmap in the second slot.
const js::Class OuterWindowProxyClass = PROXY_CLASS_DEF(
    "Proxy", JSCLASS_HAS_RESERVED_SLOTS(2)); /* additional class flags */

static const size_t OUTER_WINDOW_SLOT = 0;
static const size_t HOLDER_WEAKMAP_SLOT = 1;

class nsOuterWindowProxy : public MaybeCrossOriginObject<js::Wrapper> {
  typedef MaybeCrossOriginObject<js::Wrapper> Base;

 public:
  constexpr nsOuterWindowProxy() : Base(0) {}

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

  // Standard internal methods
  /**
   * Implementation of [[GetOwnProperty]] as defined at
   * https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty
   *
   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
   * with cx.
   */
  bool getOwnPropertyDescriptor(
      JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
      JS::MutableHandle<JS::PropertyDescriptor> desc) const override;

  /*
   * Implementation of the same-origin case of
   * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty>.
   */
  bool definePropertySameOrigin(JSContext* cx, JS::Handle<JSObject*> proxy,
                                JS::Handle<jsid> id,
                                JS::Handle<JS::PropertyDescriptor> desc,
                                JS::ObjectOpResult& result) const override;

  /**
   * Implementation of [[OwnPropertyKeys]] as defined at
   *
   * https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-ownpropertykeys
   *
   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
   * with cx.
   */
  bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy,
                       JS::AutoIdVector& props) const override;
  /**
   * Implementation of [[Delete]] as defined at
   * https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-delete
   *
   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
   * with cx.
   */
  bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
               JS::ObjectOpResult& result) const override;

  /**
   * Implementaton of hook for superclass getPrototype() method.
   */
  JSObject* getSameOriginPrototype(JSContext* cx) const override;

  /**
   * Implementation of [[HasProperty]] internal method as defined at
   * https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p
   *
   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
   * with cx.
   *
   * Note that the HTML spec does not define an override for this internal
   * method, so we just want the "normal object" behavior.  We have to override
   * it, because js::Wrapper also overrides, with "not normal" behavior.
   */
  bool has(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
           bool* bp) const override;

  /**
   * Implementation of [[Get]] internal method as defined at
   * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-get>.
   *
   * "proxy" is the WindowProxy object involved.  It may or may not be
   * same-compartment with "cx".
   *
   * "receiver" is the receiver ("this") for the get.  It will be
   * same-compartment with "cx".
   *
   * "vp" is the return value.  It will be same-compartment with "cx".
   */
  bool get(JSContext* cx, JS::Handle<JSObject*> proxy,
           JS::Handle<JS::Value> receiver, JS::Handle<jsid> id,
           JS::MutableHandle<JS::Value> vp) const override;

  /**
   * Implementation of [[Set]] internal method as defined at
   * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-set>.
   *
   * "proxy" is the WindowProxy object involved.  It may or may not be
   * same-compartment with "cx".
   *
   * "v" is the value being set.  It will be same-compartment with "cx".
   *
   * "receiver" is the receiver ("this") for the set.  It will be
   * same-compartment with "cx".
   */
  bool set(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
           JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver,
           JS::ObjectOpResult& result) const override;

  // SpiderMonkey extensions
  /**
   * Implementation of SpiderMonkey extension which just checks whether this
   * object has the property.  Basically Object.getOwnPropertyDescriptor(obj,
   * prop) !== undefined. but does not require reifying the descriptor.
   *
   * We have to override this because js::Wrapper overrides it, but we want
   * different behavior from js::Wrapper.
   *
   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
   * with cx.
   */
  bool hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
              bool* bp) const override;

  /**
   * Implementation of SpiderMonkey extension which is used as a fast path for
   * enumerating.
   *
   * We have to override this because js::Wrapper overrides it, but we want
   * different behavior from js::Wrapper.
   *
   * "proxy" is the WindowProxy object involved.  It may not be same-compartment
   * with cx.
   */
  bool getOwnEnumerablePropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy,
                                    JS::AutoIdVector& props) const override;

  /**
   * Hook used by SpiderMonkey to implement Object.prototype.toString.
   */
  const char* className(JSContext* cx,
                        JS::Handle<JSObject*> wrapper) const override;

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

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

  static const nsOuterWindowProxy singleton;

 protected:
  static nsGlobalWindowOuter* GetOuterWindow(JSObject* proxy) {
    nsGlobalWindowOuter* outerWindow =
        nsGlobalWindowOuter::FromSupports(static_cast<nsISupports*>(
            js::GetProxyReservedSlot(proxy, OUTER_WINDOW_SLOT).toPrivate()));
    return outerWindow;
  }

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

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

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

  using MaybeCrossOriginObjectMixins::EnsureHolder;
  bool EnsureHolder(JSContext* cx, JS::Handle<JSObject*> proxy,
                    JS::MutableHandle<JSObject*> holder) const override;
};

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

  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
    return "Object";
  }

  return "Window";
}

void nsOuterWindowProxy::finalize(JSFreeOp* fop, JSObject* proxy) const {
  nsGlobalWindowOuter* outerWindow = GetOuterWindow(proxy);
  if (outerWindow) {
    outerWindow->ClearWrapper(proxy);
    BrowsingContext* bc = outerWindow->GetBrowsingContext();
    if (bc) {
      bc->ClearWindowProxy();
    }

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

/**
 * IsNonConfigurableReadonlyPrimitiveGlobalProp returns true for
 * property names that fit the following criteria:
 *
 * 1) The ES spec defines a property with that name on globals.
 * 2) The property is non-configurable.
 * 3) The property is non-writable (readonly).
 * 4) The value of the property is a primitive (so doesn't change
 *    observably on when navigation happens).
 *
 * Such properties can act as actual non-configurable properties on a
 * WindowProxy, because they are not affected by navigation.
 */
#ifndef RELEASE_OR_BETA
static bool IsNonConfigurableReadonlyPrimitiveGlobalProp(JSContext* cx,
                                                         JS::Handle<jsid> id) {
  return id == GetJSIDByIndex(cx, XPCJSContext::IDX_NAN) ||
         id == GetJSIDByIndex(cx, XPCJSContext::IDX_UNDEFINED) ||
         id == GetJSIDByIndex(cx, XPCJSContext::IDX_INFINITY);
}
#endif

bool nsOuterWindowProxy::getOwnPropertyDescriptor(
    JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
    JS::MutableHandle<JS::PropertyDescriptor> desc) const {
  // First check for indexed access.  This is
  // https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty
  // step 2, mostly.
  bool found;
  if (!GetSubframeWindow(cx, proxy, id, desc.value(), found)) {
    return false;
  }
  if (found) {
    // Step 2.4.
    FillPropertyDescriptor(desc, proxy, true);
    return true;
  }

  bool isSameOrigin = IsPlatformObjectSameOrigin(cx, proxy);

  // If we did not find a subframe, we could still have an indexed property
  // access.  In that case we should throw a SecurityError in the cross-origin
  // case.
  if (!isSameOrigin && IsArrayIndex(GetArrayIndexFromId(id))) {
    // Step 2.5.2.
    return ReportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("access"));
  }

  // Step 2.5.1 is handled via the forwarding to js::Wrapper; it saves us an
  // IsArrayIndex(GetArrayIndexFromId(id)) here.  We'll never have a property on
  // the Window whose name is an index, because our defineProperty doesn't pass
  // those on to the Window.

  // Step 3.
  if (isSameOrigin) {
    // Fall through to js::Wrapper.
    {  // Scope for JSAutoRealm while we are dealing with js::Wrapper.
      // When forwarding to js::Wrapper, we should just enter the Realm of proxy
      // for now.  That's what js::Wrapper expects, and since we're same-origin
      // anyway this is not changing any security behavior.
      JSAutoRealm ar(cx, proxy);
      JS_MarkCrossZoneId(cx, id);
      bool ok = js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc);
      if (!ok) {
        return false;
      }

#ifndef RELEASE_OR_BETA  // To be turned on in bug 1496510.
      if (!IsNonConfigurableReadonlyPrimitiveGlobalProp(cx, id)) {
        desc.setConfigurable(true);
      }
#endif
    }

    // Now wrap our descriptor back into the Realm that asked for it.
    return JS_WrapPropertyDescriptor(cx, desc);
  }

  // Step 4.
  if (!CrossOriginGetOwnPropertyHelper(cx, proxy, id, desc)) {
    return false;
  }

  // Step 5
  if (desc.object()) {
    return true;
  }

  // Step 6 -- check for named subframes.
  if (JSID_IS_STRING(id)) {
    nsAutoJSString name;
    if (!name.init(cx, JSID_TO_STRING(id))) {
      return false;
    }
    nsGlobalWindowOuter* win = GetOuterWindow(proxy);
    if (RefPtr<BrowsingContext> childDOMWin = win->GetChildWindow(name)) {
      JS::Rooted<JS::Value> childValue(cx);
      if (!ToJSValue(cx, WindowProxyHolder(childDOMWin), &childValue)) {
        return false;
      }
      FillPropertyDescriptor(desc, proxy, childValue,
                             /* readonly = */ true,
                             /* enumerable = */ false);
      return true;
    }
  }

  // And step 7.
  return CrossOriginPropertyFallback(cx, proxy, id, desc);
}

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

  JS::ObjectOpResult ourResult;
  bool ok = js::Wrapper::defineProperty(cx, proxy, id, desc, ourResult);
  if (!ok) {
    return false;
  }

  if (!ourResult.ok()) {
    // It's possible that this failed because the page got the existing
    // descriptor (which we force to claim to be configurable) and then tried to
    // redefine the property with the descriptor it got but a different value.
    // We want to allow this case to succeed, so check for it and if we're in
    // that case try again but now with an attempt to define a non-configurable
    // property.
    if (!desc.hasConfigurable() || !desc.configurable()) {
      // The incoming descriptor was not explicitly marked "configurable: true",
      // so it failed for some other reason.  Just propagate that reason out.
      result = ourResult;
      return true;
    }

    JS::Rooted<JS::PropertyDescriptor> existingDesc(cx);
    ok = js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, &existingDesc);
    if (!ok) {
      return false;
    }
    if (!existingDesc.object() || existingDesc.configurable()) {
      // We have no existing property, or its descriptor is already configurable
      // (on the Window itself, where things really can be non-configurable).
      // So we failed for some other reason, which we should propagate out.
      result = ourResult;
      return true;
    }

    JS::Rooted<JS::PropertyDescriptor> updatedDesc(cx, desc);
    updatedDesc.setConfigurable(false);

    JS::ObjectOpResult ourNewResult;
    ok = js::Wrapper::defineProperty(cx, proxy, id, updatedDesc, ourNewResult);
    if (!ok) {
      return false;
    }

    if (!ourNewResult.ok()) {
      // Twiddling the configurable flag didn't help.  Just return this failure
      // out to the caller.
      result = ourNewResult;
      return true;
    }
  }

#ifndef RELEASE_OR_BETA  // To be turned on in bug 1496510.
  if (desc.hasConfigurable() && !desc.configurable() &&
      !IsNonConfigurableReadonlyPrimitiveGlobalProp(cx, id)) {
    // Give callers a way to detect that they failed to "really" define a
    // non-configurable property.
    result.failCantDefineWindowNonConfigurable();
    return true;
  }
#endif

  result.succeed();
  return true;
}

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

  if (IsPlatformObjectSameOrigin(cx, proxy)) {
    // When forwarding to js::Wrapper, we should just enter the Realm of proxy
    // for now.  That's what js::Wrapper expects, and since we're same-origin
    // anyway this is not changing any security behavior.
    JS::AutoIdVector innerProps(cx);
    {  // Scope for JSAutoRealm so we can mark the ids once we exit it
      JSAutoRealm ar(cx, proxy);
      if (!js::Wrapper::ownPropertyKeys(cx, proxy, innerProps)) {
        return false;
      }
    }
    for (auto& id : innerProps) {
      JS_MarkCrossZoneId(cx, id);
    }
    return js::AppendUnique(cx, props, innerProps);
  }

  // In the cross-origin case we purposefully exclude subframe names from the
  // list of property names we report here.
  JS::Rooted<JSObject*> holder(cx);
  if (!EnsureHolder(cx, proxy, &holder)) {
    return false;
  }

  JS::AutoIdVector crossOriginProps(cx);
  if (!js::GetPropertyKeys(cx, holder,
                           JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
                           &crossOriginProps) ||
      !js::AppendUnique(cx, props, crossOriginProps)) {
    return false;
  }

  return xpc::AppendCrossOriginWhitelistedPropNames(cx, props);
}

bool nsOuterWindowProxy::delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
                                 JS::Handle<jsid> id,
                                 JS::ObjectOpResult& result) const {
  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
    return ReportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("delete"));
  }

  if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
    // Fail (which means throw if strict, else return false).
    return result.failCantDeleteWindowElement();
  }

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

  // We're same-origin, so it should be safe to enter the Realm of "proxy".
  // Let's do that, just in case, to avoid cross-compartment issues in our
  // js::Wrapper caller..
  JSAutoRealm ar(cx, proxy);
  JS_MarkCrossZoneId(cx, id);
  return js::Wrapper::delete_(cx, proxy, id, result);
}

JSObject* nsOuterWindowProxy::getSameOriginPrototype(JSContext* cx) const {
  return Window_Binding::GetProtoObjectHandle(cx);
}

bool nsOuterWindowProxy::has(JSContext* cx, JS::Handle<JSObject*> proxy,
                             JS::Handle<jsid> id, bool* bp) const {
  // We could just directly forward this method to js::BaseProxyHandler, but
  // that involves reifying the actual property descriptor, which might be more
  // work than we have to do for has() on the Window.

  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
    // In the cross-origin case we only have own properties.  Just call hasOwn
    // directly.
    return hasOwn(cx, proxy, id, bp);
  }

  if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
    *bp = true;
    return true;
  }

  // Just to be safe in terms of compartment asserts, enter the Realm of
  // "proxy".  We're same-origin with it, so this should be safe.
  JSAutoRealm ar(cx, proxy);
  JS_MarkCrossZoneId(cx, id);
  return js::Wrapper::has(cx, proxy, id, bp);
}

bool nsOuterWindowProxy::hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy,
                                JS::Handle<jsid> id, bool* bp) const {
  // We could just directly forward this method to js::BaseProxyHandler, but
  // that involves reifying the actual property descriptor, which might be more
  // work than we have to do for hasOwn() on the Window.

  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
    // Avoiding reifying the property descriptor here would require duplicating
    // a bunch of "is this property exposed cross-origin" logic, which is
    // probably not worth it.  Just forward this along to the base
    // implementation.
    //
    // It's very important to not forward this to js::Wrapper, because that will
    // not do the right security and cross-origin checks and will pass through
    // the call to the Window.
    //
    // The BaseProxyHandler code is OK with this happening without entering the
    // compartment of "proxy".
    return js::BaseProxyHandler::hasOwn(cx, proxy, id, bp);
  }

  if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
    *bp = true;
    return true;
  }

  // Just to be safe in terms of compartment asserts, enter the Realm of
  // "proxy".  We're same-origin with it, so this should be safe.
  JSAutoRealm ar(cx, proxy);
  JS_MarkCrossZoneId(cx, id);
  return js::Wrapper::hasOwn(cx, proxy, id, bp);
}

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

  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
    return CrossOriginGet(cx, proxy, receiver, id, vp);
  }

  bool found;
  if (!GetSubframeWindow(cx, proxy, id, vp, found)) {
    return false;
  }

  if (found) {
    return true;
  }

  {  // Scope for JSAutoRealm
    // Enter "proxy"'s Realm.  We're in the same-origin case, so this should be
    // safe.
    JSAutoRealm ar(cx, proxy);

    JS_MarkCrossZoneId(cx, id);

    JS::Rooted<JS::Value> wrappedReceiver(cx, receiver);
    if (!MaybeWrapValue(cx, &wrappedReceiver)) {
      return false;
    }

    // Fall through to js::Wrapper.
    if (!js::Wrapper::get(cx, proxy, wrappedReceiver, id, vp)) {
      return false;
    }
  }

  // Make sure our return value is in the caller compartment.
  return MaybeWrapValue(cx, vp);
}

bool nsOuterWindowProxy::set(JSContext* cx, JS::Handle<JSObject*> proxy,
                             JS::Handle<jsid> id, JS::Handle<JS::Value> v,
                             JS::Handle<JS::Value> receiver,
                             JS::ObjectOpResult& result) const {
  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
    return CrossOriginSet(cx, proxy, id, v, receiver, result);
  }

  if (IsArrayIndex(GetArrayIndexFromId(id))) {
    // Reject the set.  It's up to the caller to decide whether to throw a
    // TypeError.  If the caller is strict mode JS code, it'll throw.
    return result.failReadOnly();
  }

  // Do the rest in the Realm of "proxy", since we're in the same-origin case.
  JSAutoRealm ar(cx, proxy);
  JS::Rooted<JS::Value> wrappedArg(cx, v);
  if (!MaybeWrapValue(cx, &wrappedArg)) {
    return false;
  }
  JS::Rooted<JS::Value> wrappedReceiver(cx, receiver);
  if (!MaybeWrapValue(cx, &wrappedReceiver)) {
    return false;
  }

  JS_MarkCrossZoneId(cx, id);

  return js::Wrapper::set(cx, proxy, id, wrappedArg, wrappedReceiver, result);
}

bool nsOuterWindowProxy::getOwnEnumerablePropertyKeys(
    JSContext* cx, JS::Handle<JSObject*> proxy, JS::AutoIdVector& props) const {
  // We could just stop overring getOwnEnumerablePropertyKeys and let our
  // superclasses deal (by falling back on the BaseProxyHandler implementation
  // that uses a combination of ownPropertyKeys and getOwnPropertyDescriptor to
  // only return the enumerable ones.  But maybe there's value in having
  // somewhat faster for-in iteration on Window objects...

  // Like ownPropertyKeys, our indexed stuff followed by our "normal" enumerable
  // own property names.
  if (!AppendIndexedPropertyNames(proxy, props)) {
    return false;
  }

  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
    // All the cross-origin properties other than the indexed props are
    // non-enumerable, so we're done here.
    return true;
  }

  // When forwarding to js::Wrapper, we should just enter the Realm of proxy
  // for now.  That's what js::Wrapper expects, and since we're same-origin
  // anyway this is not changing any security behavior.
  JS::AutoIdVector innerProps(cx);
  {  // Scope for JSAutoRealm so we can mark the ids once we exit it.
    JSAutoRealm ar(cx, proxy);
    if (!js::Wrapper::getOwnEnumerablePropertyKeys(cx, proxy, innerProps)) {
      return false;
    }
  }

  for (auto& id : innerProps) {
    JS_MarkCrossZoneId(cx, id);
  }

  return js::AppendUnique(cx, props, innerProps);
}

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

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

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

  nsGlobalWindowOuter* win = GetOuterWindow(proxy);
  return win->IndexedGetterOuter(index);
}

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

  return true;
}

bool nsOuterWindowProxy::EnsureHolder(
    JSContext* cx, JS::Handle<JSObject*> proxy,
    JS::MutableHandle<JSObject*> holder) const {
  return EnsureHolder(cx, proxy, HOLDER_WEAKMAP_SLOT,
                      Window_Binding::sCrossOriginAttributes,
                      Window_Binding::sCrossOriginMethods, holder);
}

size_t nsOuterWindowProxy::objectMoved(JSObject* obj, JSObject* old) const {
  nsGlobalWindowOuter* outerWindow = GetOuterWindow(obj);
  if (outerWindow) {
    outerWindow->UpdateWrapper(obj, old);
    BrowsingContext* bc = outerWindow->GetBrowsingContext();
    if (bc) {
      bc->UpdateWindowProxy(obj, old);
    }
  }
  return 0;
}

const nsOuterWindowProxy nsOuterWindowProxy::singleton;

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

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

  static const nsChromeOuterWindowProxy singleton;
};

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

  return "ChromeWindow";
}

const nsChromeOuterWindowProxy nsChromeOuterWindowProxy::singleton;

static JSObject* NewOuterWindowProxy(JSContext* cx,
                                     JS::Handle<JSObject*> global,
                                     bool isChrome) {
  MOZ_ASSERT(JS_IsGlobalObject(global));

  JSAutoRealm ar(cx, global);

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

//*****************************************************************************
//***    nsGlobalWindowOuter: Object Management
//*****************************************************************************

nsGlobalWindowOuter::nsGlobalWindowOuter(uint64_t aWindowID)
    : nsPIDOMWindowOuter(aWindowID),
      mIdleFuzzFactor(0),
      mIdleCallbackIndex(-1),
      mCurrentlyIdle(false),
      mAddActiveEventFuzzTime(true),
      mFullscreen(false),
      mFullscreenMode(false),
      mIsClosed(false),
      mInClose(false),
      mHavePendingClose(false),
      mHadOriginalOpener(false),
      mIsPopupSpam(false),
      mBlockScriptedClosingFlag(false),
      mWasOffline(false),
      mCreatingInnerWindow(false),
      mIsChrome(false),
      mAllowScriptsToClose(false),
      mTopLevelOuterContentWindow(false),
      mHasStorageAccess(false),
#ifdef DEBUG
      mSerial(0),
      mSetOpenerWindowCalled(false),
#endif
      mCleanedUp(false),
#ifdef DEBUG
      mIsValidatingTabGroup(false),
#endif
      mCanSkipCCGeneration(0),
      mAutoActivateVRDisplayID(0) {
  AssertIsOnMainThread();

  nsLayoutStatics::AddRef();

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

  // |this| is an outer window. Outer windows start out frozen and
  // remain frozen until they get an inner window.
  MOZ_ASSERT(IsFrozen());

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

#ifdef DEBUG
  mSerial = nsContentUtils::InnerOrOuterWindowCreated();

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

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

  // Add ourselves to the outer windows list.
  MOZ_ASSERT(sOuterWindowsById, "Outer Windows hash table must be created!");

  // |this| is an outer window, add to the outer windows list.
  MOZ_ASSERT(!sOuterWindowsById->Get(mWindowID),
             "This window shouldn't be in the hash table yet!");
  // We seem to see crashes in release builds because of null
  // |sOuterWindowsById|.
  if (sOuterWindowsById) {
    sOuterWindowsById->Put(mWindowID, this);
  }
}

#ifdef DEBUG

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

#endif  // DEBUG

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

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

  sOuterWindowsById = new OuterWindowByIdTable();
}

nsGlobalWindowOuter::~nsGlobalWindowOuter() {
  AssertIsOnMainThread();

  if (sOuterWindowsById) {
    MOZ_ASSERT(sOuterWindowsById->Get(mWindowID),
               "This window should be in the hash table");
    sOuterWindowsById->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);
      }
    }

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

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

  JSObject* proxy = GetWrapperMaybeDead();
  if (proxy) {
    if (mBrowsingContext) {
      mBrowsingContext->ClearWindowProxy();
    }
    js::SetProxyReservedSlot(proxy, OUTER_WINDOW_SLOT,
                             js::PrivateValue(nullptr));
  }

  // An outer window is destroyed with inner windows still possibly
  // alive, iterate through the inner windows and null out their
  // back pointer to this outer, and pull them out of the list of
  // inner windows.
  //
  // Our linked list of inner windows both contains (an nsGlobalWindowOuter),
  // and our inner windows (nsGlobalWindowInners). This means that we need to
  // use PRCList*. We can then compare that PRCList* to `this` to see if its an
  // inner or outer window.
  PRCList* w;
  while ((w = PR_LIST_HEAD(this)) != this) {
    PR_REMOVE_AND_INIT_LINK(w);
  }

  DropOuterWindowDocs();

  if (mTabGroup) {
    mTabGroup->Leave(this);
  }

  // Outer windows are always supposed to call CleanUp before letting themselves
  // be destroyed.
  MOZ_ASSERT(mCleanedUp);

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

  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
  if (obs) {
    obs->RemoveObserver(this, PERM_CHANGE_NOTIFICATION);
  }
  nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
  if (prefBranch) {
    prefBranch->RemoveObserver("network.cookie.cookieBehavior", this);
  }

  nsLayoutStatics::Release();
}

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

  delete sOuterWindowsById;
  sOuterWindowsById = nullptr;
}

void nsGlobalWindowOuter::MaybeForgiveSpamCount() {
  if (IsPopupSpamWindow()) {
    SetIsPopupSpamWindow(false);
  }
}

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

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

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

  StartDying();

  mFrames = nullptr;
  mWindowUtils = nullptr;

  ClearControllers();

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

  mArguments = nullptr;

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

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

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

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

    mControllers = nullptr;
  }
}

//*****************************************************************************
// nsGlobalWindowOuter::nsISupports
//*****************************************************************************

// QueryInterface implementation for nsGlobalWindowOuter
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGlobalWindowOuter)
  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(nsPIDOMWindowOuter)
  NS_INTERFACE_MAP_ENTRY(mozIDOMWindowProxy)
  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDOMChromeWindow, IsChromeWindow())
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
  NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
  NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGlobalWindowOuter)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGlobalWindowOuter)

NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGlobalWindowOuter)
  if (tmp->IsBlackForCC(false)) {
    if (nsCCUncollectableMarker::InGeneration(tmp->mCanSkipCCGeneration)) {
      return true;
    }
    tmp->mCanSkipCCGeneration = nsCCUncollectableMarker::sGeneration;
    if (EventListenerManager* elm = tmp->GetExistingListenerManager()) {
      elm->MarkForCC();
    }
    return true;
  }
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END

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

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

NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalWindowOuter)

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

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mArguments)

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStorage)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedDoc)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleService)

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleObservers)

  // Traverse stuff from nsPIDOMWindow
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeEventHandler)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentTarget)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageManager)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameElement)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpenerForInitialContentBrowser)

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext)

  tmp->TraverseHostObjectURIs(cb);

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mBrowserDOMWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowOuter)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mArguments)

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStorage)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuspendedDoc)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleService)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleObservers)

  // Unlink stuff from nsPIDOMWindow
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeEventHandler)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentTarget)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageManager)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameElement)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOpenerForInitialContentBrowser)

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
  if (tmp->mBrowsingContext) {
    tmp->mBrowsingContext->ClearWindowProxy();
    tmp->mBrowsingContext = nullptr;
  }

  tmp->UnlinkHostObjectURIs();

  if (tmp->IsChromeWindow()) {
    NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeFields.mBrowserDOMWindow)
  }

  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsGlobalWindowOuter)
  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END

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

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

//*****************************************************************************
// nsGlobalWindowOuter::nsIScriptGlobalObject
//*****************************************************************************

nsresult nsGlobalWindowOuter::EnsureScriptEnvironment() {
  if (GetWrapperPreserveColor()) {
    return NS_OK;
  }

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

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

  NS_ASSERTION(!mContext, "Will overwrite mContext!");

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

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

  mContext = context;
  return NS_OK;
}

nsIScriptContext* nsGlobalWindowOuter::GetScriptContext() { return mContext; }

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

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

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

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

#ifdef DEBUG
  {
    nsCOMPtr<nsIURI> uri;
    NS_GetURIWithoutRef(mDoc->GetDocumentURI(), getter_AddRefs(uri));
    NS_ASSERTION(NS_IsAboutBlank(uri), "How'd this happen?");
  }
#endif

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

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

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

  return false;
}

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

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

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

#ifdef DEBUG
    // If we have a document loaded at this point, it had better be about:blank.
    // Otherwise, something is really weird. An about:blank page has a
    // NullPrincipal.
    bool isNullPrincipal;
    MOZ_ASSERT(NS_SUCCEEDED(mDoc->NodePrincipal()->GetIsNullPrincipal(
                   &isNullPrincipal)) &&
               isNullPrincipal);
#endif
  }

  GetDocShell()->CreateAboutBlankContentViewer(newWindowPrincipal);

  if (mDoc) {
    mDoc->SetIsInitialDocument(true);
  }

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

  if (shell && !shell->DidInitialize()) {
    // Ensure that if someone plays with this document they will get
    // layout happening.
    shell->Initialize();
  }
}

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

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

  explicit WindowStateHolder(nsGlobalWindowInner* aWindow);

  nsGlobalWindowInner* GetInnerWindow() { return mInnerWindow; }

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

 protected:
  ~WindowStateHolder();

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

NS_DEFINE_STATIC_IID_ACCESSOR(WindowStateHolder, WINDOWSTATEHOLDER_IID)

WindowStateHolder::WindowStateHolder(nsGlobalWindowInner* aWindow)
    : mInnerWindow(aWindow),
      mInnerWindowReflector(RootingCx(), aWindow->GetWrapper()) {
  MOZ_ASSERT(aWindow, "null window");

  aWindow->Suspend();

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

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

NS_IMPL_ISUPPORTS(WindowStateHolder, WindowStateHolder)

bool nsGlobalWindowOuter::ComputeIsSecureContext(Document* aDocument,
                                                 SecureContextFlags aFlags) {
  nsCOMPtr<nsIPrincipal> principal = aDocument->NodePrincipal();
  if (nsContentUtils::IsSystemPrincipal(principal)) {
    return true;
  }

  // Implement https://w3c.github.io/webappsec-secure-contexts/#settings-object
  // With some modifications to allow for aFlags.

  bool hadNonSecureContextCreator = false;

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

  if (hadNonSecureContextCreator) {
    return false;
  }

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

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

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

  return false;
}

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

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

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

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

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

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

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

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

  return JS_DefineFunctions(aCx, obj, EnablePrivilegeSpec);
}

static JS::RealmCreationOptions& SelectZone(
    nsIPrincipal* aPrincipal, nsGlobalWindowInner* aNewInner,
    JS::RealmCreationOptions& aOptions) {
  // Use the shared system compartment for chrome windows.
  if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
    return aOptions.setExistingCompartment(xpc::PrivilegedJunkScope());
  }

  if (aNewInner->GetOuterWindow()) {
    nsGlobalWindowOuter* top = aNewInner->GetTopInternal();

    // If we have a top-level window, use its zone.
    if (top && top->GetGlobalJSObject()) {
      return aOptions.setNewCompartmentInExistingZone(top->GetGlobalJSObject());
    }
  }

  return aOptions.setNewCompartmentAndZone();
}

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

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

  JS::RealmOptions options;

  SelectZone(aPrincipal, aNewInner, options.creationOptions());

  options.creationOptions().setSecureContext(aIsSecureContext);

  xpc::InitGlobalObjectOptions(options, aPrincipal);

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

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

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

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

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

  return NS_OK;
}

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

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

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

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

  RefPtr<Document> oldDoc = mDoc;

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

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

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

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

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

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

  nsContentUtils::AddScriptRunner(
      NewRunnableMethod("nsGlobalWindowOuter::ClearStatus", this,
                        &nsGlobalWindowOuter::ClearStatus));

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

  nsresult rv;

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

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

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

  mContext->WillInitializeContext();

  RefPtr<nsGlobalWindowInner> currentInner = GetCurrentInnerWindowInternal();

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

  RefPtr<nsGlobalWindowInner> newInnerWindow;
  bool createdInnerWindow = false;

  bool thisChrome = IsChromeWindow();

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

  bool handleDocumentOpen = false;
  bool doomCurrentInner = false;

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

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

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

    // Inner windows are only reused for same-origin principals, but the
    // principals don't necessarily match exactly. Update the principal on the
    // realm to match the new document. NB: We don't just call
    // currentInner->RefreshRealmPrincipals() here because we haven't yet set
    // its mDoc to aDocument.
    JS::Realm* realm = js::GetNonCCWObjectRealm(newInnerGlobal);
#ifdef DEBUG
    bool sameOrigin = false;
    nsIPrincipal* existing = nsJSPrincipals::get(JS::GetRealmPrincipals(realm));
    aDocument->NodePrincipal()->Equals(existing, &sameOrigin);
    MOZ_ASSERT(sameOrigin);
    MOZ_ASSERT_IF(aDocument == oldDoc,
                  xpc::GetRealmPrincipal(realm) == aDocument->NodePrincipal());
#endif
    if (aDocument != oldDoc) {
      JS::SetRealmPrincipals(realm,
                             nsJSPrincipals::get(aDocument->NodePrincipal()));
      // Make sure we clear out the old content XBL scope, so the new one will
      // get created with a principal that subsumes our new principal.
      xpc::ClearContentXBLScope(newInnerGlobal);
    }
  } else {
    if (aState) {
      newInnerWindow = wsh->GetInnerWindow();
      newInnerGlobal = newInnerWindow->GetWrapperPreserveColor();
    } else {
      newInnerWindow = nsGlobalWindowInner::Create(this, thisChrome);
      if (StaticPrefs::dom_timeout_defer_during_load()) {
        // ensure the initial loading state is known
        newInnerWindow->SetActiveLoadingState(
            aDocument->GetReadyStateEnum() ==
            Document::ReadyState::READYSTATE_LOADING);
      }

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

      mInnerWindow = nullptr;

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

      mCreatingInnerWindow = false;
      createdInnerWindow = true;

      NS_ENSURE_SUCCESS(rv, rv);
    }

    if (currentInner && currentInner->GetWrapperPreserveColor()) {
      if (oldDoc == aDocument) {
        handleDocumentOpen = true;
      }

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

    mInnerWindow = newInnerWindow->AsInner();
    MOZ_ASSERT(mInnerWindow);
    mInnerWindow->TryToCacheTopInnerWindow();

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

      js::SetProxyReservedSlot(outer, OUTER_WINDOW_SLOT,
                               js::PrivateValue(ToSupports(this)));

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

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

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

      MOZ_ASSERT(js::IsWindowProxy(obj));

      js::SetProxyReservedSlot(obj, OUTER_WINDOW_SLOT,
                               js::PrivateValue(nullptr));
      js::SetProxyReservedSlot(outerObject, OUTER_WINDOW_SLOT,
                               js::PrivateValue(nullptr));
      js::SetProxyReservedSlot(obj, HOLDER_WEAKMAP_SLOT, JS::UndefinedValue());

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

      js::SetProxyReservedSlot(outerObject, OUTER_WINDOW_SLOT,
                               js::PrivateValue(ToSupports(this)));

      SetWrapper(outerObject);

      MOZ_ASSERT(JS::GetNonCCWObjectGlobal(outerObject) == newInnerGlobal);

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

    // Enter the new global's realm.
    JSAutoRealm ar(cx, GetWrapperPreserveColor());

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

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

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

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

  JSAutoRealm ar(cx, GetWrapperPreserveColor());

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

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

    mInnerWindow->SyncStateFromParentWindow();
  }

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

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

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

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

        // This must be called after nullifying the internal objects because
        // here we could recreate them, calling the getter methods, and store
        // them into the JS slots. If we nullify them after, the slot values and
        // the objects will be out of sync.
        newInnerWindow->ClearDocumentDependentSlots(cx);

        // When replacing an initial about:blank document we call
        // ExecutionReady again to update the client creation URL.
        rv = newInnerWindow->ExecutionReady();
        NS_ENSURE_SUCCESS(rv, rv);
      }
    } else {
      newInnerWindow->InnerSetNewDocument(cx, aDocument);

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

      rv = newInnerWindow->ExecutionReady();
      NS_ENSURE_SUCCESS(rv, rv);
    }

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

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

  // Handle any document.open() logic after we setup the new inner window
  // so that any bound DETH objects can see the top window, document, etc.
  if (handleDocumentOpen) {
    newInnerWindow->MigrateStateForDocumentOpen(currentInner);
  }

  // Tell the WindowGlobalParent that it should become the current window global
  // for our BrowsingContext if it isn't already.
  mInnerWindow->GetWindowGlobalChild()->SendBecomeCurrentWindowGlobal();

  // We no longer need the old inner window.  Start its destruction if
  // its not being reused and clear our reference.
  if (doomCurrentInner) {
    currentInner->FreeInnerObjects(handleDocumentOpen);
  }
  currentInner = nullptr;

  // Ask the JS engine to assert that it's valid to access our DocGroup whenever
  // it runs JS code for this realm. We skip the check if this window is for
  // chrome JS or an add-on.
  nsCOMPtr<nsIPrincipal> principal = mDoc->NodePrincipal();
  if (GetDocGroup() && !nsContentUtils::IsSystemPrincipal(principal) &&
      !BasePrincipal::Cast(principal)->AddonPolicy()) {
    js::SetRealmValidAccessPtr(
        cx, newInnerGlobal, newInnerWindow->GetDocGroup()->GetValidAccessPtr());
  }

  kungFuDeathGrip->DidInitializeContext();

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

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

  PreloadLocalStorage();

  // If we have a recorded interesting Large-Allocation header status, report it
  // to the newly attached document.
  ReportLargeAllocStatus();
  mLargeAllocStatus = LargeAllocStatus::NONE;

  mHasStorageAccess = false;
  nsIURI* uri = aDocument->GetDocumentURI();
  if (newInnerWindow) {
    if (StaticPrefs::network_cookie_cookieBehavior() ==
            nsICookieService::BEHAVIOR_REJECT_TRACKER &&
        nsContentUtils::IsThirdPartyWindowOrChannel(newInnerWindow, nullptr,
                                                    uri) &&
        nsContentUtils::IsTrackingResourceWindow(newInnerWindow)) {
      // Grant storage access by default if the first-party storage access
      // permission has been granted already.
      // Don't notify in this case, since we would be notifying the user
      // needlessly.
      mHasStorageAccess =
          AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
              newInnerWindow, uri, nullptr);
    }
  }

  return NS_OK;
}

void nsGlobalWindowOuter::PreloadLocalStorage() {
  if (!Storage::StoragePrefIsEnabled()) {
    return;
  }

  if (IsChromeWindow()) {
    return;
  }

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

  nsresult rv;

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

  // private browsing windows do not persist local storage to disk so we should
  // only try to precache storage when we're not a private browsing window.
  if (principal->GetPrivateBrowsingId() == 0) {
    RefPtr<Storage> storage;
    rv = storageManager->PrecacheStorage(principal, getter_AddRefs(storage));
    if (NS_SUCCEEDED(rv)) {
      mLocalStorage = storage;
    }
  }
}

void nsGlobalWindowOuter::DispatchDOMWindowCreated() {
  if (!mDoc) {
    return;
  }

  // Fire DOMWindowCreated at chrome event listeners
  nsContentUtils::DispatchChromeEvent(mDoc, ToSupports(mDoc),
                                      NS_LITERAL_STRING("DOMWindowCreated"),
                                      CanBubble::eYes, Cancelable::eNo);

  nsCOMPtr<nsIObserverService> observerService =
      mozilla::services::GetObserverService();

  // The event dispatching could possibly cause docshell destory, and
  // consequently cause mDoc to be set to nullptr by DropOuterWindowDocs(),
  // so check it again here.
  if (observerService && mDoc) {
    nsAutoString origin;
    nsIPrincipal* principal = mDoc->NodePrincipal();
    nsContentUtils::GetUTFOrigin(principal, origin);
    observerService->NotifyObservers(
        static_cast<nsIDOMWindow*>(this),
        nsContentUtils::IsSystemPrincipal(principal)
            ? "chrome-document-global-created"
            : "content-document-global-created",
        origin.get());
  }
}

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

void nsGlobalWindowOuter::SetDocShell(nsDocShell* aDocShell) {
  MOZ_ASSERT(aDocShell);

  if (aDocShell == mDocShell) {
    return;
  }

  mDocShell = aDocShell;  // Weak Reference
  mBrowsingContext = aDocShell->GetBrowsingContext();

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

  mTopLevelOuterContentWindow =
      !mIsChrome && GetScriptableTopInternal() == this;

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

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

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

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

  // Call FreeInnerObjects on all inner windows, not just the current
  // one, since some could be held by WindowStateHolder objects that
  // are GC-owned.
  RefPtr<nsGlobalWindowInner> inner;
  for (PRCList* node = PR_LIST_HEAD(this); node != this;
       node = PR_NEXT_LINK(inner)) {
    // This cast is safe because `node != this`. Non-this nodes are inner
    // windows.
    inner = static_cast<nsGlobalWindowInner*>(node);
    MOZ_ASSERT(!inner->mOuterWindow || inner->mOuterWindow == this);
    inner->FreeInnerObjects();
  }

  // Don't report that we were detached to the nsWindowMemoryReporter, as it
  // only tracks inner windows.

  NotifyWindowIDDestroyed("outer-window-destroyed");

  nsGlobalWindowInner* currentInner = GetCurrentInnerWindowInternal();

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

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

    // Release our document reference
    DropOuterWindowDocs();
  }

  ClearControllers();

  mChromeEventHandler = nullptr;  // force release now

  if (mContext) {
    // When we're about to destroy a top level content window
    // (for example a tab), we trigger a full GC by passing null as the last
    // param. We also trigger a full GC for chrome windows.
    nsJSContext::PokeGC(JS::GCReason::SET_DOC_SHELL,
                        (mTopLevelOuterContentWindow || mIsChrome)
                            ? nullptr
                            : GetWrapperPreserveColor());
    mContext = nullptr;
  }

  mDocShell = nullptr;  // Weak Reference

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

  MaybeForgiveSpamCount();
  CleanUp();
}

void nsGlobalWindowOuter::SetOpenerWindow(nsPIDOMWindowOuter* aOpener,
                                          bool aOriginalOpener) {
  nsWeakPtr opener = do_GetWeakReference(aOpener);
  if (opener == mOpener) {
    MOZ_DIAGNOSTIC_ASSERT(
        !aOpener || !aOpener->GetDocShell() ||
        (GetBrowsingContext() &&
         GetBrowsingContext()->GetOpener() == aOpener->GetBrowsingContext()));
    return;
  }

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

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

  if (mDocShell) {
    MOZ_DIAGNOSTIC_ASSERT(
        !aOriginalOpener || !aOpener ||
        // TODO(farre): Allowing to set a closed or closing window as
        // opener is not ideal, since it won't have a docshell and
        // therefore no browsing context. This means that we're
        // effectively setting the browsing context opener to null and
        // the window opener to a closed window. This needs to be
        // cleaned up, see Bug 1511353.
        nsGlobalWindowOuter::Cast(aOpener)->IsClosedOrClosing() ||
        aOpener->GetBrowsingContext() == GetBrowsingContext()->GetOpener());
    // TODO(farre): Here we really wish to only consider the case
    // where 'aOriginalOpener'. See bug 1509016.
    GetBrowsingContext()->SetOpener(aOpener ? aOpener->GetBrowsingContext()
                                            : nullptr);
  }

  // Check that the js visible opener matches! We currently don't depend on this
  // being true outside of nightly, so we disable the assertion in optimized
  // release / beta builds.
  nsPIDOMWindowOuter* contentOpener = GetSanitizedOpener(aOpener);

  // contentOpener is not used when the DIAGNOSTIC_ASSERT is compiled out.
  mozilla::Unused << contentOpener;
  MOZ_DIAGNOSTIC_ASSERT(
      !contentOpener || !mTabGroup ||
      mTabGroup == nsGlobalWindowOuter::Cast(contentOpener)->mTabGroup);

  if (aOriginalOpener) {
    MOZ_ASSERT(!mHadOriginalOpener,
               "Probably too late to call ComputeIsSecureContext again");
    mHadOriginalOpener = true;
  }

#ifdef DEBUG
  mSetOpenerWindowCalled = true;
#endif
}

void nsGlobalWindowOuter::UpdateParentTarget() {
  // NOTE: This method is nearly identical to
  // nsGlobalWindowInner::UpdateParentTarget(). IF YOU UPDATE THIS METHOD,
  // UPDATE THE OTHER ONE TOO!  The one difference is that this method updates
  // mMessageManager as well, which inner windows don't have.

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

  if (!mMessageManager) {
    nsGlobalWindowOuter* topWin = GetScriptableTopInternal();
    if (topWin) {
      frameElement = topWin->GetFrameElementInternal();
      mMessageManager = nsContentUtils::TryGetTabChildGlobal(frameElement);
    }
  }

  if (!mMessageManager) {
    mMessageManager = nsContentUtils::TryGetTabChildGlobal(mChromeEventHandler);
  }

  if (mMessageManager) {
    mParentTarget = mMessageManager;
  } else {
    mParentTarget = mChromeEventHandler;
  }
}

EventTarget* nsGlobalWindowOuter::GetTargetForEventTargetChain() {
  return GetCurrentInnerWindowInternal();
}

void nsGlobalWindowOuter::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
  MOZ_CRASH("The outer window should not be part of an event path");
}

bool nsGlobalWindowOuter::ShouldPromptToBlockDialogs() {
  nsGlobalWindowOuter* topWindowOuter = GetScriptableTopInternal();
  if (!topWindowOuter) {
    NS_ASSERTION(!mDocShell,
                 "ShouldPromptToBlockDialogs() called without a top window?");
    return true;
  }

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

  return topWindow->DialogsAreBeingAbused();
}

bool nsGlobalWindowOuter::AreDialogsEnabled() {
  nsGlobalWindowOuter* topWindowOuter = GetScriptableTopInternal();
  if (!topWindowOuter) {
    NS_ERROR("AreDialogsEnabled() called without a top window?");
    return false;
  }

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

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

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

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

  return topWindow->mAreDialogsEnabled;
}

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

  if (!promptSvc) {
    return true;
  }

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

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

  return true;
}

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

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

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

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

nsresult nsGlobalWindowOuter::PostHandleEvent(EventChainPostVisitor& aVisitor) {
  MOZ_CRASH("The outer window should not be part of an event path");
}

void nsGlobalWindowOuter::PoisonOuterWindowProxy(JSObject* aObject) {
  if (aObject == GetWrapperMaybeDead()) {
    PoisonWrapper();
  }
}

nsresult nsGlobalWindowOuter::SetArguments(nsIArray* aArguments) {
  nsresult rv;

  // Historically, we've used the same machinery to handle openDialog arguments
  // (exposed via window.arguments) and showModalDialog arguments (exposed via
  // window.dialogArguments), even though the former is XUL-only and uses an
  // XPCOM array while the latter is web-exposed and uses an arbitrary JS value.
  // Moreover, per-spec |dialogArguments| is a property of the browsing context
  // (outer), whereas |arguments| lives on the inner.
  //
  // We've now mostly separated them, but the difference is still opaque to
  // nsWindowWatcher (the caller of SetArguments in this little back-and-forth
  // embedding waltz we do here).
  //
  // So we need to demultiplex the two cases here.
  nsGlobalWindowInner* currentInner = GetCurrentInnerWindowInternal();

  mArguments = aArguments;
  rv = currentInner->DefineArgumentsProperty(aArguments);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

//*****************************************************************************
// nsGlobalWindowOuter::nsIScriptObjectPrincipal
//*****************************************************************************

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

//*****************************************************************************
// nsGlobalWindowOuter::nsIDOMWindow
//*****************************************************************************

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

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

  MOZ_ASSERT(piWin == this);

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

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

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

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

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

Navigator* nsGlobalWindowOuter::GetNavigator() {
  FORWARD_TO_INNER(Navigator, (), nullptr);
}

nsScreen* nsGlobalWindowOuter::GetScreen() {
  FORWARD_TO_INNER(GetScreen, (IgnoreErrors()), nullptr);
}

void nsPIDOMWindowOuter::MaybeActiveMediaComponents() {
  if (mMediaSuspend != nsISuspendedTypes::SUSPENDED_BLOCK) {
    return;
  }

  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
          ("nsPIDOMWindowOuter, MaybeActiveMediaComponents, "
           "resume the window from blocked, this = %p\n",
           this));

  SetMediaSuspend(nsISuspendedTypes::NONE_SUSPENDED);
}

SuspendTypes nsPIDOMWindowOuter::GetMediaSuspend() const {
  return mMediaSuspend;
}

void nsPIDOMWindowOuter::SetMediaSuspend(SuspendTypes aSuspend) {
  if (!IsDisposableSuspend(aSuspend)) {
    MaybeNotifyMediaResumedFromBlock(aSuspend);
    mMediaSuspend = aSuspend;
  }

  RefreshMediaElementsSuspend(aSuspend);
}

void nsPIDOMWindowOuter::MaybeNotifyMediaResumedFromBlock(
    SuspendTypes aSuspend) {
  if (mMediaSuspend == nsISuspendedTypes::SUSPENDED_BLOCK &&
      aSuspend == nsISuspendedTypes::NONE_SUSPENDED) {
    RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
    if (service) {
      service->NotifyMediaResumedFromBlock(GetOuterWindow());
    }
  }
}

bool nsPIDOMWindowOuter::GetAudioMuted() const { return mAudioMuted; }

void nsPIDOMWindowOuter::SetAudioMuted(bool aMuted) {
  if (mAudioMuted == aMuted) {
    return;
  }

  mAudioMuted = aMuted;
  RefreshMediaElementsVolume();
}

float nsPIDOMWindowOuter::GetAudioVolume() const { return mAudioVolume; }

nsresult nsPIDOMWindowOuter::SetAudioVolume(float aVolume) {
  if (aVolume < 0.0) {
    return NS_ERROR_DOM_INDEX_SIZE_ERR;
  }

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

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

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

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

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

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

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

Nullable<WindowProxyHolder> nsGlobalWindowOuter::GetParentOuter() {
  nsPIDOMWindowOuter* parent = GetScriptableParent();
  BrowsingContext* parentBC;
  if (!parent || !(parentBC = parent->GetBrowsingContext())) {
    return nullptr;
  }

  return WindowProxyHolder(parentBC);
}

/**
 * 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* nsGlobalWindowOuter::GetScriptableParent() {
  if (!mDocShell) {
    return nullptr;
  }

  if (mDocShell->GetIsMozBrowser()) {
    return this;
  }

  nsCOMPtr<nsPIDOMWindowOuter> parent = GetParent();
  return parent;
}

/**
 * Behavies identically to GetScriptableParent extept that it returns null
 * if GetScriptableParent would return this window.
 */
nsPIDOMWindowOuter* nsGlobalWindowOuter::GetScriptableParentOrNull() {
  nsPIDOMWindowOuter* parent = GetScriptableParent();
  return (nsGlobalWindowOuter::Cast(parent) == this) ? nullptr : parent;
}

/**
 * nsPIDOMWindow::GetParent (when called from C++) is just a wrapper around
 * GetRealParent.
 */
already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::GetParent() {
  if (!mDocShell) {
    return nullptr;
  }

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

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

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

static nsresult GetTopImpl(nsGlobalWindowOuter* aWin, nsIURI* aURIBeingLoaded,
                           nsPIDOMWindowOuter** aTop, bool aScriptable,
                           bool aExcludingExtensionAccessibleContentFrames) {
  *aTop = nullptr;

  MOZ_ASSERT_IF(aExcludingExtensionAccessibleContentFrames, !aScriptable);

  // Walk up the parent chain.

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

    prevParent = parent;

    if (aScriptable) {
      parent = parent->GetScriptableParent();
    } else {
      parent = parent->GetParent();
    }

    if (aExcludingExtensionAccessibleContentFrames) {
      if (auto* p = nsGlobalWindowOuter::Cast(parent)) {
        nsGlobalWindowInner* currentInner = p->GetCurrentInnerWindowInternal();
        nsIURI* uri = prevParent->GetDocumentURI();
        if (!uri) {
          // If our parent doesn't have a URI yet, we have a document that is in
          // the process of being loaded.  In that case, our caller is
          // responsible for passing in the URI for the document that is being
          // loaded, so we fall back to using that URI here.
          uri = aURIBeingLoaded;
        }

        if (currentInner && uri) {
          // If we find an inner window, we better find the uri for the current
          // window we're looking at.  If we can't find it directly, it is the
          // responsibility of our caller to provide it to us.
          MOZ_DIAGNOSTIC_ASSERT(uri);

          // If the new parent has permission to load the current page, we're
          // at a moz-extension:// frame which has a host permission that allows
          // it to load the document that we've loaded.  In that case, stop at
          // this frame and consider it the top-level frame.
          //
          // Note that it's possible for the set of URIs accepted by
          // AddonAllowsLoad() to change at runtime, but we don't need to cache
          // the result of this check, since the important consumer of this code
          // (which is nsIHttpChannelInternal.topWindowURI) already caches the
          // result after computing it the first time.
          if (BasePrincipal::Cast(p->GetPrincipal())
                  ->AddonAllowsLoad(uri, true)) {
            parent = prevParent;
            break;
          }
        }
      }
    }

  } while (parent != prevParent);

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

  return NS_OK;
}

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

already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::GetTop() {
  nsCOMPtr<nsPIDOMWindowOuter> window;
  GetTopImpl(this, /* aURIBeingLoaded = */ nullptr, getter_AddRefs(window),
             /* aScriptable = */ false,
             /* aExcludingExtensionAccessibleContentFrames = */ false);
  return window.forget();
}

already_AddRefed<nsPIDOMWindowOuter>
nsGlobalWindowOuter::GetTopExcludingExtensionAccessibleContentFrames(
    nsIURI* aURIBeingLoaded) {
  nsCOMPtr<nsPIDOMWindowOuter> window;
  GetTopImpl(this, aURIBeingLoaded, getter_AddRefs(window),
             /* aScriptable = */ false,
             /* aExcludingExtensionAccessibleContentFrames = */ true);
  return window.forget();
}

void nsGlobalWindowOuter::GetContentOuter(JSContext* aCx,
                                          JS::MutableHandle<JSObject*> aRetval,
                                          CallerType aCallerType,
                                          ErrorResult& aError) {
  nsCOMPtr<nsPIDOMWindowOuter> content =
      GetContentInternal(aError, aCallerType);
  if (aError.Failed()) {
    return;
  }

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

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

  aRetval.set(nullptr);
}

already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::GetContentInternal(
    ErrorResult& aError, CallerType aCallerType) {
  // First check for a named frame named "content"
  RefPtr<BrowsingContext> bc = GetChildWindow(NS_LITERAL_STRING("content"));
  if (bc) {
    nsCOMPtr<nsPIDOMWindowOuter> content(bc->GetDOMWindow());
    return content.forget();
  }

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

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

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

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

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

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

  if (!primaryContent) {
    return nullptr;
  }

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

nsresult nsGlobalWindowOuter::GetPrompter(nsIPrompt** aPrompt) {
  if (!mDocShell) return NS_ERROR_FAILURE;

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

  prompter.forget(aPrompt);
  return NS_OK;
}

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

bool nsGlobalWindowOuter::Closed() { return GetClosedOuter(); }

nsDOMWindowList* nsGlobalWindowOuter::GetFrames() {
  if (!mFrames && mDocShell) {
    mFrames = new nsDOMWindowList(mDocShell);
  }

  return mFrames;
}

already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::IndexedGetterOuter(
    uint32_t aIndex) {
  nsDOMWindowList* windows = GetFrames();
  NS_ENSURE_TRUE(windows, nullptr);

  return windows->IndexedGetter(aIndex);
}

nsIControllers* nsGlobalWindowOuter::GetControllersOuter(ErrorResult& aError) {
  if (!mControllers) {
    mControllers = new nsXULControllers();
    if (!mControllers) {
      aError.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }

    // Add in the default controller
    nsCOMPtr<nsIController> controller =
        nsBaseCommandController::CreateWindowController();
    if (!controller) {
      aError.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }

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

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

  return mControllers;
}

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

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

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

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

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

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

  return nullptr;
}

nsPIDOMWindowOuter* nsGlobalWindowOuter::GetOpenerWindowOuter() {
  nsCOMPtr<nsPIDOMWindowOuter> opener = do_QueryReferent(mOpener);

  if (!opener) {
    return nullptr;
  }

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

  return GetSanitizedOpener(opener);
}

already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::GetOpener() {
  nsCOMPtr<nsPIDOMWindowOuter> opener = GetOpenerWindowOuter();
  return opener.forget();
}

void nsGlobalWindowOuter::GetStatusOuter(nsAString& aStatus) {
  aStatus = mStatus;
}

void nsGlobalWindowOuter::SetStatusOuter(const nsAString& aStatus) {
  mStatus = aStatus;

  // We don't support displaying window.status in the UI, so there's nothing
  // left to do here.
}

void nsGlobalWindowOuter::GetNameOuter(nsAString& aName) {
  if (mDocShell) {
    mDocShell->GetName(aName);
  }
}

void nsGlobalWindowOuter::SetNameOuter(const nsAString& aName,
                                       mozilla::ErrorResult& aError) {
  if (mDocShell) {
    aError = mDocShell->SetName(aName);
  }
}

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

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

  return presContext->DevPixelsToIntCSSPixels(px);
}

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

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

  return presContext->CSSPixelsToDevPixels(px);
}

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

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

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

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

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

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

nsresult nsGlobalWindowOuter::GetInnerSize(CSSIntSize& aSize) {
  EnsureSizeAndPositionUpToDate();

  NS_ENSURE_STATE(mDocShell);

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

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

  // If the visual viewport has been overriden, return that.
  if (presShell->IsVisualViewportSizeSet()) {
    aSize = CSSIntRect::FromAppUnitsRounded(presShell->GetVisualViewportSize());
    return NS_OK;
  }

  // Whether or not the css viewport has been overridden, we can get the
  // correct value by looking at the visible area of the presContext.
  RefPtr<nsViewManager> viewManager = presShell->GetViewManager();
  if (viewManager) {
    viewManager->FlushDelayedResize(false);
  }

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

int32_t nsGlobalWindowOuter::GetInnerWidthOuter(ErrorResult& aError) {
  CSSIntSize size;
  aError = GetInnerSize(size);
  return size.width;
}

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

void nsGlobalWindowOuter::SetInnerWidthOuter(int32_t aInnerWidth,
                                             CallerType aCallerType,
                                             ErrorResult& aError) {
  if (!mDocShell) {
    aError.Throw(NS_ERROR_UNEXPECTED);
    return;
  }

  CheckSecurityWidthAndHeight(&aInnerWidth, nullptr, aCallerType);
  RefPtr<nsIPresShell> presShell = mDocShell->GetPresShell();

  // Setting inner width should set the visual viewport. Most of the
  // time, this is the same as the CSS viewport, and when we set one,
  // we implicitly set both of them. But if
  // presShell->IsVisualViewportSizeSet() returns true, that means
  // that the two diverge. In that case we only set the visual viewport.
  // This mirrors the logic in ::GetInnerSize() and ensures that JS
  // behaves sanely when setting and then getting the innerWidth.
  if (presShell && presShell->IsVisualViewportSizeSet()) {
    CSSSize viewportSize =
        CSSRect::FromAppUnits(presShell->GetVisualViewportSize());
    viewportSize.width = aInnerWidth;
    nsLayoutUtils::SetVisualViewportSize(presShell, viewportSize);
    return;
  }

  // We're going to set both viewports. If the css viewport has been
  // overridden, change the css viewport override. The visual viewport
  // will adopt this value via the logic in ::GetInnerSize().
  if (presShell && presShell->GetIsViewportOverridden()) {
    nscoord height = 0;

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

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

  // Nothing has been overriden, so change the docshell itself, which will
  // affect both viewports.
  int32_t height = 0;
  int32_t unused = 0;

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

int32_t nsGlobalWindowOuter::GetInnerHeightOuter(ErrorResult& aError) {
  CSSIntSize size;
  aError = GetInnerSize(size);
  return size.height;
}

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

void nsGlobalWindowOuter::SetInnerHeightOuter(int32_t aInnerHeight,
                                              CallerType aCallerType,
                                              ErrorResult& aError) {
  if (!mDocShell) {
    aError.Throw(NS_ERROR_UNEXPECTED);
    return;
  }

  CheckSecurityWidthAndHeight(nullptr, &aInnerHeight, aCallerType);
  RefPtr<nsIPresShell> presShell = mDocShell->GetPresShell();

  // Setting inner height should set the visual viewport. Most of the
  // time, this is the same as the CSS viewport, and when we set one,
  // we implicitly set both of them. But if
  // presShell->IsVisualViewportSizeSet() returns true, that means
  // that the two diverge. In that case we only set the visual viewport.
  // This mirrors the logic in ::GetInnerSize() and ensures that JS
  // behaves sanely when setting and then getting the innerHeight.
  if (presShell && presShell->IsVisualViewportSizeSet()) {
    CSSSize viewportSize =
        CSSRect::FromAppUnits(presShell->GetVisualViewportSize());
    viewportSize.height = aInnerHeight;
    nsLayoutUtils::SetVisualViewportSize(presShell, viewportSize);
    return;
  }

  // We're going to set both viewports. If the css viewport has been
  // overridden, change the css viewport override. The visual viewport
  // will adopt this value via the logic in ::GetInnerSize().
  if (presShell && presShell->GetIsViewportOverridden()) {
    nscoord width = 0;

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

    nsRect shellArea = presContext->GetVisibleArea();
    width = shellArea.Width();
    SetCSSViewportWidthAndHeight(
        width, nsPresContext::CSSPixelsToAppUnits(aInnerHeight));
    return;
  }

  // Nothing has been overriden, so change the docshell itself, which will
  // affect both viewports.
  int32_t height = 0;
  int32_t width = 0;

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

nsIntSize nsGlobalWindowOuter::GetOuterSize(CallerType aCallerType,
                                            ErrorResult& aError) {
  if (nsContentUtils::ResistFingerprinting(aCallerType)) {
    CSSIntSize size;
    aError = GetInnerSize(size);
    return nsIntSize(size.width, size.height);
  }

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

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

  return DevToCSSIntPixels(sizeDevPixels);
}

int32_t nsGlobalWindowOuter::GetOuterWidthOuter(CallerType aCallerType,
                                                ErrorResult& aError) {
  return GetOuterSize(aCallerType, aError).width;
}

int32_t nsGlobalWindowOuter::GetOuterHeightOuter(CallerType aCallerType,
                                                 ErrorResult& aError) {
  return GetOuterSize(aCallerType, aError).height;
}

void nsGlobalWindowOuter::SetOuterSize(int32_t aLengthCSSPixels, bool aIsWidth,
                                       CallerType aCallerType,
                                       ErrorResult& aError) {
  nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
  if (!treeOwnerAsWin) {
    aError.Throw(NS_ERROR_FAILURE);
    return;
  }

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

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

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

  CheckForDPIChange();
}

void nsGlobalWindowOuter::SetOuterWidthOuter(int32_t aOuterWidth,
                                             CallerType aCallerType,
                                             ErrorResult& aError) {
  SetOuterSize(aOuterWidth, true, aCallerType, aError);
}

void nsGlobalWindowOuter::SetOuterHeightOuter(int32_t aOuterHeight,
                                              CallerType aCallerType,
                                              ErrorResult& aError) {
  SetOuterSize(aOuterHeight, false, aCallerType, aError);
}

CSSIntPoint nsGlobalWindowOuter::GetScreenXY(CallerType aCallerType,
                                             ErrorResult& aError) {
  // When resisting fingerprinting, always return (0,0)
  if (nsContentUtils::ResistFingerprinting(aCallerType)) {
    return CSSIntPoint(0, 0);
  }

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

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

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

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

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

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

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

int32_t nsGlobalWindowOuter::GetScreenXOuter(CallerType aCallerType,
                                             ErrorResult& aError) {
  return GetScreenXY(aCallerType, aError).x;
}

nsRect nsGlobalWindowOuter::GetInnerScreenRect() {
  if (!mDocShell) {
    return nsRect();
  }

  EnsureSizeAndPositionUpToDate();

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

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

  return rootFrame->GetScreenRectInAppUnits();
}

float nsGlobalWindowOuter::GetMozInnerScreenXOuter(CallerType aCallerType) {
  // When resisting fingerprinting, always return 0.
  if (nsContentUtils::ResistFingerprinting(aCallerType)) {
    return 0.0;
  }

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

float nsGlobalWindowOuter::GetMozInnerScreenYOuter(CallerType aCallerType) {
  // Return 0 to prevent fingerprinting.
  if (nsContentUtils::ResistFingerprinting(aCallerType)) {
    return 0.0;
  }

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

double nsGlobalWindowOuter::GetDevicePixelRatioOuter(CallerType aCallerType) {
  if (!mDocShell) {
    return 1.0;
  }

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

  if (nsContentUtils::ResistFingerprinting(aCallerType)) {
    return 1.0;
  }

  float overrideDPPX = presContext->GetOverrideDPPX();

  if (overrideDPPX > 0) {
    return overrideDPPX;
  }

  return double(AppUnitsPerCSSPixel()) /
         double(presContext->AppUnitsPerDevPixel());
}

float nsPIDOMWindowOuter::GetDevicePixelRatio(CallerType aCallerType) {
  return nsGlobalWindowOuter::Cast(this)->GetDevicePixelRatioOuter(aCallerType);
}

uint64_t nsGlobalWindowOuter::GetMozPaintCountOuter() {
  if (!mDocShell) {
    return 0;
  }

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

already_AddRefed<MediaQueryList> nsGlobalWindowOuter::MatchMediaOuter(
    const nsAString& aMediaQueryList, CallerType aCallerType) {
  if (!mDoc) {
    return nullptr;
  }

  return mDoc->MatchMedia(aMediaQueryList, aCallerType);
}

void nsGlobalWindowOuter::SetScreenXOuter(int32_t aScreenX,
                                          CallerType aCallerType,
                                          ErrorResult& aError) {
  nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
  if (!treeOwnerAsWin) {
    aError.Throw(NS_ERROR_FAILURE);
    return;
  }

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

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

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

  CheckForDPIChange();
}

int32_t nsGlobalWindowOuter::GetScreenYOuter(CallerType aCallerType,
                                             ErrorResult& aError) {
  return GetScreenXY(aCallerType, aError).y;
}

void nsGlobalWindowOuter::SetScreenYOuter(int32_t aScreenY,
                                          CallerType aCallerType,
                                          ErrorResult& aError) {
  nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
  if (!treeOwnerAsWin) {
    aError.Throw(NS_ERROR_FAILURE);
    return;
  }

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

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

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

  CheckForDPIChange();
}

// NOTE: Arguments to this function should have values scaled to
// CSS pixels, not device pixels.
void nsGlobalWindowOuter::CheckSecurityWidthAndHeight(int32_t* aWidth,
                                                      int32_t* aHeight,
                                                      CallerType aCallerType) {
#ifdef MOZ_XUL
  if (aCallerType != CallerType::System) {
    // if attempting to resize the window, hide any open popups
    nsContentUtils::HidePopupsInDocument(mDoc);
  }
#endif

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

    if (aCallerType != CallerType::System) {
      // sec check failed
      if (aWidth && *aWidth < 100) {
        *aWidth = 100;
      }
      if (aHeight && *aHeight < 100) {
        *aHeight = 100;
      }
    }
  }
}

// NOTE: Arguments to this function should have values in device pixels
nsresult nsGlobalWindowOuter::SetDocShellWidthAndHeight(int32_t aInnerWidth,
                                                        int32_t aInnerHeight) {
  NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);

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

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

  return NS_OK;
}

// NOTE: Arguments to this function should have values in app units
void nsGlobalWindowOuter::SetCSSViewportWidthAndHeight(nscoord aInnerWidth,
                                                       nscoord aInnerHeight) {
  RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();

  nsRect shellArea = presContext->GetVisibleArea();
  shellArea.SetHeight(aInnerHeight);
  shellArea.SetWidth(aInnerWidth);

  presContext->SetVisibleArea(shellArea);
}

// NOTE: Arguments to this function should have values scaled to
// CSS pixels, not device pixels.
void nsGlobalWindowOuter::CheckSecurityLeftAndTop(int32_t* aLeft, int32_t* aTop,
                                                  CallerType aCallerType) {
  // This one is harder. We have to get the screen size and window dimensions.

  // Check security state for use in determing window dimensions

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

    if (nsGlobalWindowOuter* rootWindow =
            nsGlobalWindowOuter::Cast(GetPrivateRoot())) {
      rootWindow->FlushPendingNotifications(FlushType::Layout);
    }

    nsCOMPtr<nsIBaseWindow> treeOwner = GetTreeOwnerWindow();

    RefPtr<nsScreen> screen = GetScreen();

    if (treeOwner && screen) {
      int32_t winLeft, winTop, winWidth, winHeight;

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

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

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

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

int32_t nsGlobalWindowOuter::GetScrollBoundaryOuter(Side aSide) {
  FlushPendingNotifications(FlushType::Layout);
  if (nsIScrollableFrame* sf = GetScrollFrame()) {
    return nsPresContext::AppUnitsToIntCSSPixels(
        sf->GetScrollRange().Edge(aSide));
  }
  return 0;
}

CSSPoint nsGlobalWindowOuter::GetScrollXY(bool aDoFlush) {
  if (aDoFlush) {
    FlushPendingNotifications(FlushType::Layout);
  } else {
    EnsureSizeAndPositionUpToDate();
  }

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

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

  return CSSPoint::FromAppUnits(scrollPos);
}

double nsGlobalWindowOuter::GetScrollXOuter() { return GetScrollXY(false).x; }

double nsGlobalWindowOuter::GetScrollYOuter() { return GetScrollXY(false).y; }

uint32_t nsGlobalWindowOuter::Length() {
  nsDOMWindowList* windows = GetFrames();

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

Nullable<WindowProxyHolder> nsGlobalWindowOuter::GetTopOuter() {
  nsCOMPtr<nsPIDOMWindowOuter> top = GetScriptableTop();
  BrowsingContext* topBC;
  if (!top || !(topBC = top->GetBrowsingContext())) {
    return nullptr;
  }
  return WindowProxyHolder(topBC);
}

already_AddRefed<BrowsingContext> nsGlobalWindowOuter::GetChildWindow(
    const nsAString& aName) {
  NS_ENSURE_TRUE(mBrowsingContext, nullptr);

  return mBrowsingContext->FindChildWithName(aName);
}

bool nsGlobalWindowOuter::DispatchCustomEvent(const nsAString& aEventName) {
  bool defaultActionEnabled = true;
  nsContentUtils::DispatchTrustedEvent(mDoc, ToSupports(this), aEventName,
                                       CanBubble::eYes, Cancelable::eYes,
                                       &defaultActionEnabled);

  return defaultActionEnabled;
}

bool nsGlobalWindowOuter::DispatchResizeEvent(const CSSIntSize& aSize) {
  ErrorResult res;
  RefPtr<Event> domEvent = mDoc->CreateEvent(NS_LITERAL_STRING("CustomEvent"),
                                             CallerType::System, res);
  if (res.Failed()) {
    return false;
  }

  // We don't init the AutoJSAPI with ourselves because we don't want it
  // reporting errors to our onerror handlers.
  AutoJSAPI jsapi;
  jsapi.Init();
  JSContext* cx = jsapi.cx();
  JSAutoRealm ar(cx, GetWrapperPreserveColor());

  DOMWindowResizeEventDetail detail;
  detail.mWidth = aSize.width;
  detail.mHeight = aSize.height;
  JS::Rooted<JS::Value> detailValue(cx);
  if (!ToJSValue(cx, detail, &detailValue)) {
    return false;
  }

  CustomEvent* customEvent = static_cast<CustomEvent*>(domEvent.get());
  customEvent->InitCustomEvent(cx, NS_LITERAL_STRING("DOMWindowResize"),
                               /* aCanBubble = */ true,
                               /* aCancelable = */ true, detailValue);

  domEvent->SetTrusted(true);
  domEvent->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;

  nsCOMPtr<EventTarget> target = do_QueryInterface(GetOuterWindow());
  domEvent->SetTarget(target);

  return target->DispatchEvent(*domEvent, CallerType::System, IgnoreErrors());
}

static already_AddRefed<nsIDocShellTreeItem> GetCallerDocShellTreeItem() {
  nsCOMPtr<nsIWebNavigation> callerWebNav = do_GetInterface(GetEntryGlobal());
  nsCOMPtr<nsIDocShellTreeItem> callerItem = do_QueryInterface(callerWebNav);

  return callerItem.forget();
}

bool nsGlobalWindowOuter::WindowExists(const nsAString& aName,
                                       bool aForceNoOpener,
                                       bool aLookForCallerOnJSStack) {
  MOZ_ASSERT(mDocShell, "Must have docshell");

  if (aForceNoOpener) {
    return aName.LowerCaseEqualsLiteral("_self") ||
           aName.LowerCaseEqualsLiteral("_top") ||
           aName.LowerCaseEqualsLiteral("_parent");
  }

  nsCOMPtr<nsIDocShellTreeItem> caller;
  if (aLookForCallerOnJSStack) {
    caller = GetCallerDocShellTreeItem();
  }

  if (!caller) {
    caller = mDocShell;
  }

  nsCOMPtr<nsIDocShellTreeItem> namedItem;
  mDocShell->FindItemWithName(aName, nullptr, caller,
                              /* aSkipTabGroup = */ false,
                              getter_AddRefs(namedItem));
  return namedItem != nullptr;
}

already_AddRefed<nsIWidget> nsGlobalWindowOuter::GetMainWidget() {
  nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();

  nsCOMPtr<nsIWidget> widget;

  if (treeOwnerAsWin) {
    treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget));
  }

  return widget.forget();
}

nsIWidget* nsGlobalWindowOuter::GetNearestWidget() const {
  nsIDocShell* docShell = GetDocShell();
  NS_ENSURE_TRUE(docShell, nullptr);
  nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
  NS_ENSURE_TRUE(presShell, nullptr);
  nsIFrame* rootFrame = presShell->GetRootFrame();
  NS_ENSURE_TRUE(rootFrame, nullptr);
  return rootFrame->GetView()->GetNearestWidget(nullptr);
}

void nsGlobalWindowOuter::SetFullscreenOuter(bool aFullscreen,
                                             mozilla::ErrorResult& aError) {
  aError =
      SetFullscreenInternal(FullscreenReason::ForFullscreenMode, aFullscreen);
}

nsresult nsGlobalWindowOuter::SetFullScreen(bool aFullscreen) {
  return SetFullscreenInternal(FullscreenReason::ForFullscreenMode,
                               aFullscreen);
}

static void FinishDOMFullscreenChange(Document* aDoc, bool aInDOMFullscreen) {
  if (aInDOMFullscreen) {
    // Ask the document to handle any pending DOM fullscreen change.
    if (!Document::HandlePendingFullscreenRequests(aDoc)) {
      // If we don't end up having anything in fullscreen,
      // async request exiting fullscreen.
      Document::AsyncExitFullscreen(aDoc);
    }
  } else {
    // If the window is leaving fullscreen state, also ask the document
    // to exit from DOM Fullscreen.
    Document::ExitFullscreenInDocTree(aDoc);
  }
}

struct FullscreenTransitionDuration {
  // The unit of the durations is millisecond
  uint16_t mFadeIn = 0;
  uint16_t mFadeOut = 0;
  bool IsSuppressed() const { return mFadeIn == 0 && mFadeOut == 0; }
};

static void GetFullscreenTransitionDuration(
    bool aEnterFullscreen, FullscreenTransitionDuration* aDuration) {
  const char* pref = aEnterFullscreen
                         ? "full-screen-api.transition-duration.enter"
                         : "full-screen-api.transition-duration.leave";
  nsAutoCString prefValue;
  Preferences::GetCString(pref, prefValue);
  if (!prefValue.IsEmpty()) {
    sscanf(prefValue.get(), "%hu%hu", &aDuration->mFadeIn,
           &aDuration->mFadeOut);
  }
}

class FullscreenTransitionTask : public Runnable {
 public:
  FullscreenTransitionTask(const FullscreenTransitionDuration& aDuration,
                           nsGlobalWindowOuter* aWindow, bool aFullscreen,
                           nsIWidget* aWidget, nsIScreen* aScreen,
                           nsISupports* aTransitionData)
      : mozilla::Runnable("FullscreenTransitionTask"),
        mWindow(aWindow),
        mWidget(aWidget),
        mScreen(aScreen),
        mTransitionData(aTransitionData),
        mDuration(aDuration),
        mStage(eBeforeToggle),
        mFullscreen(aFullscreen) {}

  NS_IMETHOD Run() override;

 private:
  ~FullscreenTransitionTask() override {}

  /**
   * The flow of fullscreen transition:
   *
   *         parent process         |         child process
   * ----------------------------------------------------------------
   *
   *                                    | request/exit fullscreen
   *                              <-----|
   *         BeforeToggle stage |
   *                            |
   *  ToggleFullscreen stage *1 |----->
   *                                    | HandleFullscreenRequests
   *                                    |
   *                              <-----| MozAfterPaint event
   *       AfterToggle stage *2 |
   *                            |
   *                  End stage |
   *
   * Note we also start a timer at *1 so that if we don't get MozAfterPaint
   * from the child process in time, we continue going to *2.
   */
  enum Stage {
    // BeforeToggle stage happens before we enter or leave fullscreen
    // state. In this stage, the task triggers the pre-toggle fullscreen
    // transition on the widget.
    eBeforeToggle,
    // ToggleFullscreen stage actually executes the fullscreen toggle,
    // and wait for the next paint on the content to continue.
    eToggleFullscreen,
    // AfterToggle stage happens after we toggle the fullscreen state.
    // In this stage, the task triggers the post-toggle fullscreen
    // transition on the widget.
    eAfterToggle,
    // End stage is triggered after the final transition finishes.
    eEnd
  };

  class Observer final : public nsIObserver {
   public:
    NS_DECL_ISUPPORTS
    NS_DECL_NSIOBSERVER

    explicit Observer(FullscreenTransitionTask* aTask) : mTask(aTask) {}

   private:
    ~Observer() = default;

    RefPtr<FullscreenTransitionTask> mTask;
  };

  static const char* const kPaintedTopic;

  RefPtr<nsGlobalWindowOuter> mWindow;
  nsCOMPtr<nsIWidget> mWidget;
  nsCOMPtr<nsIScreen> mScreen;
  nsCOMPtr<nsITimer> mTimer;
  nsCOMPtr<nsISupports> mTransitionData;

  TimeStamp mFullscreenChangeStartTime;
  FullscreenTransitionDuration mDuration;
  Stage mStage;
  bool mFullscreen;
};

const char* const FullscreenTransitionTask::kPaintedTopic =
    "fullscreen-painted";

NS_IMETHODIMP
FullscreenTransitionTask::Run() {
  Stage stage = mStage;
  mStage = Stage(mStage + 1);
  if (MOZ_UNLIKELY(mWidget->Destroyed())) {
    // If the widget has been destroyed before we get here, don't try to
    // do anything more. Just let it go and release ourselves.
    NS_WARNING("The widget to fullscreen has been destroyed");
    return NS_OK;
  }
  if (stage == eBeforeToggle) {
    PROFILER_ADD_MARKER("Fullscreen transition start", DOM);
    mWidget->PerformFullscreenTransition(nsIWidget::eBeforeFullscreenToggle,
                                         mDuration.mFadeIn, mTransitionData,
                                         this);
  } else if (stage == eToggleFullscreen) {
    PROFILER_ADD_MARKER("Fullscreen toggle start", DOM);
    mFullscreenChangeStartTime = TimeStamp::Now();
    if (MOZ_UNLIKELY(mWindow->mFullscreen != mFullscreen)) {
      // This could happen in theory if several fullscreen requests in
      // different direction happen continuously in a short time. We
      // need to ensure the fullscreen state matches our target here,
      // otherwise the widget would change the window state as if we
      // toggle for Fullscreen Mode instead of Fullscreen API.
      NS_WARNING("The fullscreen state of the window does not match");
      mWindow->mFullscreen = mFullscreen;
    }
    // Toggle the fullscreen state on the widget
    if (!mWindow->SetWidgetFullscreen(FullscreenReason::ForFullscreenAPI,
                                      mFullscreen, mWidget, mScreen)) {
      // Fail to setup the widget, call FinishFullscreenChange to
      // complete fullscreen change directly.
      mWindow->FinishFullscreenChange(mFullscreen);
    }
    // Set observer for the next content paint.
    nsCOMPtr<nsIObserver> observer = new Observer(this);
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    obs->AddObserver(observer, kPaintedTopic, false);
    // There are several edge cases where we may never get the paint
    // notification, including:
    // 1. the window/tab is closed before the next paint;
    // 2. the user has switched to another tab before we get here.
    // Completely fixing those cases seems to be tricky, and since they
    // should rarely happen, it probably isn't worth to fix. Hence we
    // simply add a timeout here to ensure we never hang forever.
    // In addition, if the page is complicated or the machine is less
    // powerful, layout could take a long time, in which case, staying
    // in black screen for that long could hurt user experience even
    // more than exposing an intermediate state.
    uint32_t timeout =
        Preferences::GetUint("full-screen-api.transition.timeout", 1000);
    NS_NewTimerWithObserver(getter_AddRefs(mTimer), observer, timeout,
                            nsITimer::TYPE_ONE_SHOT);
  } else if (stage == eAfterToggle) {
    Telemetry::AccumulateTimeDelta(Telemetry::FULLSCREEN_TRANSITION_BLACK_MS,
                                   mFullscreenChangeStartTime);
    mWidget->PerformFullscreenTransition(nsIWidget::eAfterFullscreenToggle,
                                         mDuration.mFadeOut, mTransitionData,
                                         this);
  } else if (stage == eEnd) {
    PROFILER_ADD_MARKER("Fullscreen transition end", DOM);
    mWidget->CleanupFullscreenTransition();
  }
  return NS_OK;
}

NS_IMPL_ISUPPORTS(FullscreenTransitionTask::Observer, nsIObserver)

NS_IMETHODIMP
FullscreenTransitionTask::Observer::Observe(nsISupports* aSubject,
                                            const char* aTopic,
                                            const char16_t* aData) {
  bool shouldContinue = false;
  if (strcmp(aTopic, FullscreenTransitionTask::kPaintedTopic) == 0) {
    nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(aSubject));
    nsCOMPtr<nsIWidget> widget =
        win ? nsGlobalWindowInner::Cast(win)->GetMainWidget() : nullptr;
    if (widget == mTask->mWidget) {
      // The paint notification arrives first. Cancel the timer.
      mTask->mTimer->Cancel();
      shouldContinue = true;
      PROFILER_ADD_MARKER("Fullscreen toggle end", DOM);
    }
  } else {
#ifdef DEBUG
    MOZ_ASSERT(strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0,
               "Should only get fullscreen-painted or timer-callback");
    nsCOMPtr<nsITimer> timer(do_QueryInterface(aSubject));
    MOZ_ASSERT(timer && timer == mTask->mTimer,
               "Should only trigger this with the timer the task created");
#endif
    shouldContinue = true;
    PROFILER_ADD_MARKER("Fullscreen toggle timeout", DOM);
  }
  if (shouldContinue) {
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    obs->RemoveObserver(this, kPaintedTopic);
    mTask->mTimer = nullptr;
    mTask->Run();
  }
  return NS_OK;
}

static bool MakeWidgetFullscreen(nsGlobalWindowOuter* aWindow,
                                 FullscreenReason aReason, bool aFullscreen) {
  nsCOMPtr<nsIWidget> widget = aWindow->GetMainWidget();
  if (!widget) {
    return false;
  }

  FullscreenTransitionDuration duration;
  bool performTransition = false;
  nsCOMPtr<nsISupports> transitionData;
  if (aReason == FullscreenReason::ForFullscreenAPI) {
    GetFullscreenTransitionDuration(aFullscreen, &duration);
    if (!duration.IsSuppressed()) {
      performTransition = widget->PrepareForFullscreenTransition(
          getter_AddRefs(transitionData));
    }
  }
  // We pass nullptr as the screen to SetWidgetFullscreen
  // and FullscreenTransitionTask, as we do not wish to override
  // the default screen selection behavior.  The screen containing
  // most of the widget will be selected.
  if (!performTransition) {
    return aWindow->SetWidgetFullscreen(aReason, aFullscreen, widget, nullptr);
  }
  nsCOMPtr<nsIRunnable> task = new FullscreenTransitionTask(
      duration, aWindow, aFullscreen, widget, nullptr, transitionData);
  task->Run();
  return true;
}

nsresult nsGlobalWindowOuter::SetFullscreenInternal(FullscreenReason aReason,
                                                    bool aFullscreen) {
  MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(),
             "Requires safe to run script as it "
             "may call FinishDOMFullscreenChange");

  NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);

  MOZ_ASSERT(
      aReason != FullscreenReason::ForForceExitFullscreen || !aFullscreen,
      "FullscreenReason::ForForceExitFullscreen can "
      "only be used with exiting fullscreen");

  // Only chrome can change our fullscreen mode. Otherwise, the state
  // can only be changed for DOM fullscreen.
  if (aReason == FullscreenReason::ForFullscreenMode &&
      !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
    return NS_OK;
  }

  // SetFullscreen needs to be called on the root window, so get that
  // via the DocShell tree, and if we are not already the root,
  // call SetFullscreen on that window instead.
  nsCOMPtr<nsIDocShellTreeItem> rootItem;
  mDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
  nsCOMPtr<nsPIDOMWindowOuter> window =
      rootItem ? rootItem->GetWindow() : nullptr;
  if (!window) return NS_ERROR_FAILURE;
  if (rootItem != mDocShell)
    return window->SetFullscreenInternal(aReason, aFullscreen);

  // make sure we don't try to set full screen on a non-chrome window,
  // which might happen in embedding world
  if (mDocShell->ItemType() != nsIDocShellTreeItem::typeChrome)
    return NS_ERROR_FAILURE;

  // If we are already in full screen mode, just return.
  if (mFullscreen == aFullscreen) {
    return NS_OK;
  }

  // Note that although entering DOM fullscreen could also cause
  // consequential calls to this method, those calls will be skipped
  // at the condition above.
  if (aReason == FullscreenReason::ForFullscreenMode) {
    if (!aFullscreen && !mFullscreenMode) {
      // If we are exiting fullscreen mode, but we actually didn't
      // entered fullscreen mode, the fullscreen state was only for
      // the Fullscreen API. Change the reason here so that we can
      // perform transition for it.
      aReason = FullscreenReason::ForFullscreenAPI;
    } else {
      mFullscreenMode = aFullscreen;
    }
  } else {
    // If we are exiting from DOM fullscreen while we initially make
    // the window fullscreen because of fullscreen mode, don't restore
    // the window. But we still need to exit the DOM fullscreen state.
    if (!aFullscreen && mFullscreenMode) {
      FinishDOMFullscreenChange(mDoc, false);
      return NS_OK;
    }
  }

  // Prevent chrome documents which are still loading from resizing
  // the window after we set fullscreen mode.
  nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
  nsCOMPtr<nsIXULWindow> xulWin(do_GetInterface(treeOwnerAsWin));
  if (aFullscreen && xulWin) {
    xulWin->SetIntrinsicallySized(false);
  }

  // Set this before so if widget sends an event indicating its
  // gone full screen, the state trap above works.
  mFullscreen = aFullscreen;

  // Sometimes we don't want the top-level widget to actually go fullscreen,
  // for example in the B2G desktop client, we don't want the emulated screen
  // dimensions to appear to increase when entering fullscreen mode; we just
  // want the content to fill the entire client area of the emulator window.
  if (!Preferences::GetBool("full-screen-api.ignore-widgets", false)) {
    if (MakeWidgetFullscreen(this, aReason, aFullscreen)) {
      // The rest of code for switching fullscreen is in nsGlobalWindowOuter::
      // FinishFullscreenChange() which will be called after sizemodechange
      // event is dispatched.
      return NS_OK;
    }
  }

  FinishFullscreenChange(aFullscreen);
  return NS_OK;
}

bool nsGlobalWindowOuter::SetWidgetFullscreen(FullscreenReason aReason,
                                              bool aIsFullscreen,
                                              nsIWidget* aWidget,
                                              nsIScreen* aScreen) {
  MOZ_ASSERT(this == GetTopInternal(), "Only topmost window should call this");
  MOZ_ASSERT(!GetFrameElementInternal(), "Content window should not call this");
  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);

  if (!NS_WARN_IF(!IsChromeWindow())) {
    if (!NS_WARN_IF(mChromeFields.mFullscreenPresShell)) {
      if (nsIPresShell* shell = mDocShell->GetPresShell()) {
        if (nsRefreshDriver* rd = shell->GetRefreshDriver()) {
          mChromeFields.mFullscreenPresShell = do_GetWeakReference(shell);
          MOZ_ASSERT(mChromeFields.mFullscreenPresShell);
          rd->SetIsResizeSuppressed();
          rd->Freeze();
        }
      }
    }
  }
  nsresult rv =
      aReason == FullscreenReason::ForFullscreenMode
          ?
          // If we enter fullscreen for fullscreen mode, we want
          // the native system behavior.
          aWidget->MakeFullScreenWithNativeTransition(aIsFullscreen, aScreen)
          : aWidget->MakeFullScreen(aIsFullscreen, aScreen);
  return NS_SUCCEEDED(rv);
}

/* virtual */ void nsGlobalWindowOuter::FullscreenWillChange(
    bool aIsFullscreen) {
  if (aIsFullscreen) {
    DispatchCustomEvent(NS_LITERAL_STRING("willenterfullscreen"));
  } else {
    DispatchCustomEvent(NS_LITERAL_STRING("willexitfullscreen"));
  }
}

/* virtual */ void nsGlobalWindowOuter::FinishFullscreenChange(
    bool aIsFullscreen) {
  if (aIsFullscreen != mFullscreen) {
    NS_WARNING("Failed to toggle fullscreen state of the widget");
    // We failed to make the widget enter fullscreen.
    // Stop further changes and restore the state.
    if (!aIsFullscreen) {
      mFullscreen = false;
      mFullscreenMode = false;
    } else {
      MOZ_ASSERT_UNREACHABLE("Failed to exit fullscreen?");
      mFullscreen = true;
      // We don't know how code can reach here. Not sure
      // what value should be set for fullscreen mode.
      mFullscreenMode = false;
    }
    return;
  }

  // Note that we must call this to toggle the DOM fullscreen state
  // of the document before dispatching the "fullscreen" event, so
  // that the chrome can distinguish between browser fullscreen mode
  // and DOM fullscreen.
  FinishDOMFullscreenChange(mDoc, mFullscreen);

  // dispatch a "fullscreen" DOM event so that XUL apps can
  // respond visually if we are kicked into full screen mode
  DispatchCustomEvent(NS_LITERAL_STRING("fullscreen"));

  if (!NS_WARN_IF(!IsChromeWindow())) {
    if (nsCOMPtr<nsIPresShell> shell =
            do_QueryReferent(mChromeFields.mFullscreenPresShell)) {
      if (nsRefreshDriver* rd = shell->GetRefreshDriver()) {
        rd->Thaw();
      }
      mChromeFields.mFullscreenPresShell = nullptr;
    }
  }

  if (!mWakeLock && mFullscreen) {
    RefPtr<power::PowerManagerService> pmService =
        power::PowerManagerService::GetInstance();
    if (!pmService) {
      return;
    }

    // XXXkhuey using the inner here, do we need to do something if it changes?
    ErrorResult rv;
    mWakeLock = pmService->NewWakeLock(NS_LITERAL_STRING("DOM_Fullscreen"),
                                       GetCurrentInnerWindow(), rv);
    NS_WARNING_ASSERTION(!rv.Failed(), "Failed to lock the wakelock");
    rv.SuppressException();
  } else if (mWakeLock && !mFullscreen) {
    ErrorResult rv;
    mWakeLock->Unlock(rv);
    mWakeLock = nullptr;
    rv.SuppressException();
  }
}

bool nsGlobalWindowOuter::Fullscreen() const {
  NS_ENSURE_TRUE(mDocShell, mFullscreen);

  // Get the fullscreen value of the root window, to always have the value
  // accurate, even when called from content.
  nsCOMPtr<nsIDocShellTreeItem> rootItem;
  mDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
  if (rootItem == mDocShell) {
    if (!XRE_IsContentProcess()) {
      // We are the root window. Return our internal value.
      return mFullscreen;
    }
    if (nsCOMPtr<nsIWidget> widget = GetNearestWidget()) {
      // We are in content process, figure out the value from
      // the sizemode of the puppet widget.
      return widget->SizeMode() == nsSizeMode_Fullscreen;
    }
    return false;
  }

  nsCOMPtr<nsPIDOMWindowOuter> window = rootItem->GetWindow();
  NS_ENSURE_TRUE(window, mFullscreen);

  return nsGlobalWindowOuter::Cast(window)->Fullscreen();
}

bool nsGlobalWindowOuter::GetFullscreenOuter() { return Fullscreen(); }

bool nsGlobalWindowOuter::GetFullScreen() {
  FORWARD_TO_INNER(GetFullScreen, (), false);
}

void nsGlobalWindowOuter::EnsureReflowFlushAndPaint() {
  NS_ASSERTION(mDocShell,
               "EnsureReflowFlushAndPaint() called with no "
               "docshell!");

  if (!mDocShell) return;

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

  if (!presShell) return;

  // Flush pending reflows.
  if (mDoc) {
    mDoc->FlushPendingNotifications(FlushType::Layout);
  }

  // Unsuppress painting.
  presShell->UnsuppressPainting();
}

// static
void nsGlobalWindowOuter::MakeScriptDialogTitle(
    nsAString& aOutTitle, nsIPrincipal* aSubjectPrincipal) {
  MOZ_ASSERT(aSubjectPrincipal);

  aOutTitle.Truncate();

  // Try to get a host from the running principal -- this will do the
  // right thing for javascript: and data: documents.

  nsCOMPtr<nsIURI> uri;
  nsresult rv = aSubjectPrincipal->GetURI(getter_AddRefs(uri));
  if (NS_SUCCEEDED(rv) && uri) {
    // remove user:pass for privacy and spoof prevention

    nsCOMPtr<nsIURIFixup> fixup(components::URIFixup::Service());
    if (fixup) {
      nsCOMPtr<nsIURI> fixedURI;
      rv = fixup->CreateExposableURI(uri, getter_AddRefs(fixedURI));
      if (NS_SUCCEEDED(rv) && fixedURI) {
        nsAutoCString host;
        fixedURI->GetHost(host);

        if (!host.IsEmpty()) {
          // if this URI has a host we'll show it. For other
          // schemes (e.g. file:) we fall back to the localized
          // generic string

          nsAutoCString prepath;
          fixedURI->GetDisplayPrePath(prepath);

          NS_ConvertUTF8toUTF16 ucsPrePath(prepath);
          const char16_t* formatStrings[] = {ucsPrePath.get()};
          nsContentUtils::FormatLocalizedString(
              nsContentUtils::eCOMMON_DIALOG_PROPERTIES, "ScriptDlgHeading",
              formatStrings, aOutTitle);
        }
      }
    }
  }

  if (aOutTitle.IsEmpty()) {
    // We didn't find a host so use the generic heading
    nsContentUtils::GetLocalizedString(
        nsContentUtils::eCOMMON_DIALOG_PROPERTIES, "ScriptDlgGenericHeading",
        aOutTitle);
  }

  // Just in case
  if (aOutTitle.IsEmpty()) {
    NS_WARNING(
        "could not get ScriptDlgGenericHeading string from string bundle");
    aOutTitle.AssignLiteral("[Script]");
  }
}

bool nsGlobalWindowOuter::CanMoveResizeWindows(CallerType aCallerType) {
  // When called from chrome, we can avoid the following checks.
  if (aCallerType != CallerType::System) {
    // Don't allow scripts to move or resize windows that were not opened by a
    // script.
    if (!mHadOriginalOpener) {
      return false;
    }

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

    // Ignore the request if we have more than one tab in the window.
    if (XRE_IsContentProcess()) {
      nsCOMPtr<nsIDocShell> docShell = GetDocShell();
      if (docShell) {
        nsCOMPtr<nsITabChild> child = docShell->GetTabChild();
        bool hasSiblings = true;
        if (child && NS_SUCCEEDED(child->GetHasSiblings(&hasSiblings)) &&
            hasSiblings) {
          return false;
        }
      }
    } else {
      nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
      uint32_t itemCount = 0;
      if (treeOwner && NS_SUCCEEDED(treeOwner->GetTabCount(&itemCount)) &&
          itemCount > 1) {
        return false;
      }
    }
  }

  if (mDocShell) {
    bool allow;
    nsresult rv = mDocShell->GetAllowWindowControl(&allow);
    if (NS_SUCCEEDED(rv) && !allow) return false;
  }

  if (nsGlobalWindowInner::sMouseDown &&
      !nsGlobalWindowInner::sDragServiceDisabled) {
    nsCOMPtr<nsIDragService> ds =
        do_GetService("@mozilla.org/widget/dragservice;1");
    if (ds) {
      nsGlobalWindowInner::sDragServiceDisabled = true;
      ds->Suppress();
    }
  }
  return true;
}

bool nsGlobalWindowOuter::AlertOrConfirm(bool aAlert, const nsAString& aMessage,
                                         nsIPrincipal& aSubjectPrincipal,
                                         ErrorResult& aError) {
  // XXX This method is very similar to nsGlobalWindowOuter::Prompt, make
  // sure any modifications here don't need to happen over there!
  if (!AreDialogsEnabled()) {
    // Just silently return.  In the case of alert(), the return value is
    // ignored.  In the case of confirm(), returning false is the same thing as
    // would happen if the user cancels.
    return false;
  }

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

  // Before bringing up the window, unsuppress painting and flush
  // pending reflows.
  EnsureReflowFlushAndPaint();

  nsAutoString title;
  MakeScriptDialogTitle(title, &aSubjectPrincipal);

  // Remove non-terminating null characters from the
  // string. See bug #310037.
  nsAutoString final;
  nsContentUtils::StripNullChars(aMessage, final);
  nsContentUtils::PlatformToDOMLineBreaks(final);

  nsresult rv;
  nsCOMPtr<nsIPromptFactory> promptFac =
      do_GetService("@mozilla.org/prompter;1", &rv);
  if (NS_FAILED(rv)) {
    aError.Throw(rv);
    return false;
  }

  nsCOMPtr<nsIPrompt> prompt;
  aError =
      promptFac->GetPrompt(this, NS_GET_IID(nsIPrompt), getter_AddRefs(prompt));
  if (aError.Failed()) {
    return false;
  }

  // Always allow tab modal prompts for alert and confirm.
  if (nsCOMPtr<nsIWritablePropertyBag2> promptBag = do_QueryInterface(prompt)) {
    promptBag->SetPropertyAsBool(NS_LITERAL_STRING("allowTabModal"), true);
  }

  bool result = false;
  nsAutoSyncOperation sync(mDoc);
  if (ShouldPromptToBlockDialogs()) {
    bool disallowDialog = false;
    nsAutoString label;
    nsContentUtils::GetLocalizedString(
        nsContentUtils::eCOMMON_DIALOG_PROPERTIES, "ScriptDialogLabel", label);

    aError = aAlert
                 ? prompt->AlertCheck(title.get(), final.get(), label.get(),
                                      &disallowDialog)
                 : prompt->ConfirmCheck(title.get(), final.get(), label.get(),
                                        &disallowDialog, &result);

    if (disallowDialog) DisableDialogs();
  } else {
    aError = aAlert ? prompt->Alert(title.get(), final.get())
                    : prompt->Confirm(title.get(), final.get(), &result);
  }

  return result;
}

void nsGlobalWindowOuter::AlertOuter(const nsAString& aMessage,
                                     nsIPrincipal& aSubjectPrincipal,
                                     ErrorResult& aError) {
  AlertOrConfirm(/* aAlert = */ true, aMessage, aSubjectPrincipal, aError);
}

bool nsGlobalWindowOuter::ConfirmOuter(const nsAString& aMessage,
                                       nsIPrincipal& aSubjectPrincipal,
                                       ErrorResult& aError) {
  return AlertOrConfirm(/* aAlert = */ false, aMessage, aSubjectPrincipal,
                        aError);
}

void nsGlobalWindowOuter::PromptOuter(const nsAString& aMessage,
                                      const nsAString& aInitial,
                                      nsAString& aReturn,
                                      nsIPrincipal& aSubjectPrincipal,
                                      ErrorResult& aError) {
  // XXX This method is very similar to nsGlobalWindowOuter::AlertOrConfirm,
  // make sure any modifications here don't need to happen over there!
  SetDOMStringToNull(aReturn);

  if (!AreDialogsEnabled()) {
    // Return null, as if the user just canceled the prompt.
    return;
  }

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

  // Before bringing up the window, unsuppress painting and flush
  // pending reflows.
  EnsureReflowFlushAndPaint();

  nsAutoString title;
  MakeScriptDialogTitle(title, &aSubjectPrincipal);

  // Remove non-terminating null characters from the
  // string. See bug #310037.
  nsAutoString fixedMessage, fixedInitial;
  nsContentUtils::StripNullChars(aMessage, fixedMessage);
  nsContentUtils::PlatformToDOMLineBreaks(fixedMessage);
  nsContentUtils::StripNullChars(aInitial, fixedInitial);

  nsresult rv;
  nsCOMPtr<nsIPromptFactory> promptFac =
      do_GetService("@mozilla.org/prompter;1", &rv);
  if (NS_FAILED(rv)) {
    aError.Throw(rv);
    return;
  }

  nsCOMPtr<nsIPrompt> prompt;
  aError =
      promptFac->GetPrompt(this, NS_GET_IID(nsIPrompt), getter_AddRefs(prompt));
  if (aError.Failed()) {
    return;
  }

  // Always allow tab modal prompts for prompt.
  if (nsCOMPtr<nsIWritablePropertyBag2> promptBag = do_QueryInterface(prompt)) {
    promptBag->SetPropertyAsBool(NS_LITERAL_STRING("allowTabModal"), true);
  }

  // Pass in the default value, if any.
  char16_t* inoutValue = ToNewUnicode(fixedInitial);
  bool disallowDialog = false;

  nsAutoString label;
  label.SetIsVoid(true);
  if (ShouldPromptToBlockDialogs()) {
    nsContentUtils::GetLocalizedString(
        nsContentUtils::eCOMMON_DIALOG_PROPERTIES, "ScriptDialogLabel", label);
  }

  nsAutoSyncOperation sync(mDoc);
  bool ok;
  aError = prompt->Prompt(title.get(), fixedMessage.get(), &inoutValue,
                          label.IsVoid() ? nullptr : label.get(),
                          &disallowDialog, &ok);

  if (disallowDialog) {
    DisableDialogs();
  }

  if (aError.Failed()) {
    return;
  }

  nsString outValue;
  outValue.Adopt(inoutValue);

  if (ok && inoutValue) {
    aReturn.Assign(outValue);
  }
}

void nsGlobalWindowOuter::FocusOuter() {
  nsFocusManager* fm = nsFocusManager::GetFocusManager();
  if (!fm) {
    return;
  }

  nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(mDocShell);

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

  if (!isVisible) {
    // A hidden tab is being focused, ignore this call.
    return;
  }

  nsCOMPtr<nsPIDOMWindowInner> caller = do_QueryInterface(GetEntryGlobal());
  nsPIDOMWindowOuter* callerOuter = caller ? caller->GetOuterWindow() : nullptr;
  nsCOMPtr<nsPIDOMWindowOuter> opener = GetOpener();

  // Enforce dom.disable_window_flip (for non-chrome), but still allow the
  // window which opened us to raise us at times when popups are allowed
  // (bugs 355482 and 369306).
  bool canFocus = CanSetProperty("dom.disable_window_flip") ||
                  (opener == callerOuter &&
                   RevisePopupAbuseLevel(PopupBlocker::GetPopupControlState()) <
                       PopupBlocker::openBlocked);

  nsCOMPtr<mozIDOMWindowProxy> activeDOMWindow;
  fm->GetActiveWindow(getter_AddRefs(activeDOMWindow));

  nsCOMPtr<nsIDocShellTreeItem> rootItem;
  mDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
  nsCOMPtr<nsPIDOMWindowOuter> rootWin =
      rootItem ? rootItem->GetWindow() : nullptr;
  auto* activeWindow = nsPIDOMWindowOuter::From(activeDOMWindow);
  bool isActive = (rootWin == activeWindow);

  nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
  if (treeOwnerAsWin && (canFocus || isActive)) {
    bool isEnabled = true;
    if (NS_SUCCEEDED(treeOwnerAsWin->GetEnabled(&isEnabled)) && !isEnabled) {
      NS_WARNING("Should not try to set the focus on a disabled window");
      return;
    }

    // XXXndeakin not sure what this is for or if it should go somewhere else
    nsCOMPtr<nsIEmbeddingSiteWindow> embeddingWin(
        do_GetInterface(treeOwnerAsWin));
    if (embeddingWin) embeddingWin->SetFocus();
  }

  if (!mDocShell) {
    return;
  }

  nsCOMPtr<nsIPresShell> presShell;
  // Don't look for a presshell if we're a root chrome window that's got
  // about:blank loaded.  We don't want to focus our widget in that case.
  // XXXbz should we really be checking for IsInitialDocument() instead?
  bool lookForPresShell = true;
  if (mDocShell->ItemType() == nsIDocShellTreeItem::typeChrome &&
      GetPrivateRoot() == this && mDoc) {
    nsIURI* ourURI = mDoc->GetDocumentURI();
    if (ourURI) {
      lookForPresShell = !NS_IsAboutBlank(ourURI);
    }
  }

  if (lookForPresShell) {
    presShell = mDocShell->GetEldestPresShell();
  }

  nsCOMPtr<nsIDocShellTreeItem> parentDsti;
  mDocShell->GetParent(getter_AddRefs(parentDsti));

  // set the parent's current focus to the frame containing this window.
  nsCOMPtr<nsPIDOMWindowOuter> parent =
      parentDsti ? parentDsti->GetWindow() : nullptr;
  if (parent) {
    nsCOMPtr<Document> parentdoc = parent->GetDoc();
    if (!parentdoc) {
      return;
    }

    RefPtr<Element> frame = parentdoc->FindContentForSubDocument(mDoc);
    if (frame) {
      uint32_t flags = nsIFocusManager::FLAG_NOSCROLL;
      if (canFocus) flags |= nsIFocusManager::FLAG_RAISE;
      DebugOnly<nsresult> rv = fm->SetFocus(frame, flags);
      MOZ_ASSERT(NS_SUCCEEDED(rv),
                 "SetFocus only fails if the first argument is null, "
                 "but we pass an element");
    }
    return;
  }

  if (canFocus) {
    // if there is no parent, this must be a toplevel window, so raise the
    // window if canFocus is true. If this is a child process, the raise
    // window request will get forwarded to the parent by the puppet widget.
    DebugOnly<nsresult> rv = fm->SetActiveWindow(this);
    MOZ_ASSERT(NS_SUCCEEDED(rv),
               "SetActiveWindow only fails if passed null or a non-toplevel "
               "window, which is not the case here.");
  }
}

nsresult nsGlobalWindowOuter::Focus() {
  FORWARD_TO_INNER(Focus, (), NS_ERROR_UNEXPECTED);
}

void nsGlobalWindowOuter::BlurOuter() {
  // If dom.disable_window_flip == true, then content should not be allowed
  // to call this function (this would allow popunders, bug 369306)
  if (!CanSetProperty("dom.disable_window_flip")) {
    return;
  }

  // If embedding apps don't implement nsIEmbeddingSiteWindow, we
  // shouldn't throw exceptions to web content.

  nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
  nsCOMPtr<nsIEmbeddingSiteWindow> siteWindow(do_GetInterface(treeOwner));
  if (siteWindow) {
    // This method call may cause mDocShell to become nullptr.
    siteWindow->Blur();

    // if the root is focused, clear the focus
    nsIFocusManager* fm = nsFocusManager::GetFocusManager();
    if (fm && mDoc) {
      RefPtr<Element> element;
      fm->GetFocusedElementForWindow(this, false, nullptr,
                                     getter_AddRefs(element));
      if (element == mDoc->GetRootElement()) {
        fm->ClearFocus(this);
      }
    }
  }
}

void nsGlobalWindowOuter::StopOuter(ErrorResult& aError) {
  nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
  if (webNav) {
    aError = webNav->Stop(nsIWebNavigation::STOP_ALL);
  }
}

void nsGlobalWindowOuter::PrintOuter(ErrorResult& aError) {
#ifdef NS_PRINTING
  if (!AreDialogsEnabled()) {
    // We probably want to keep throwing here; silently doing nothing is a bit
    // weird given the typical use cases of print().
    aError.Throw(NS_ERROR_NOT_AVAILABLE);
    return;
  }

  if (ShouldPromptToBlockDialogs() && !ConfirmDialogIfNeeded()) {
    aError.Throw(NS_ERROR_NOT_AVAILABLE);
    return;
  }

  nsCOMPtr<nsIWebBrowserPrint> webBrowserPrint;
  if (NS_SUCCEEDED(GetInterface(NS_GET_IID(nsIWebBrowserPrint),
                                getter_AddRefs(webBrowserPrint)))) {
    nsAutoSyncOperation sync(GetCurrentInnerWindowInternal()
                                 ? GetCurrentInnerWindowInternal()->mDoc.get()
                                 : nullptr);

    nsCOMPtr<nsIPrintSettingsService> printSettingsService =
        do_GetService("@mozilla.org/gfx/printsettings-service;1");

    nsCOMPtr<nsIPrintSettings> printSettings;
    if (printSettingsService) {
      bool printSettingsAreGlobal =
          Preferences::GetBool("print.use_global_printsettings", false);

      if (printSettingsAreGlobal) {
        printSettingsService->GetGlobalPrintSettings(
            getter_AddRefs(printSettings));

        nsAutoString printerName;
        printSettings->GetPrinterName(printerName);

        bool shouldGetDefaultPrinterName = printerName.IsEmpty();
#  ifdef MOZ_X11
        // In Linux, GTK backend does not support per printer settings.
        // Calling GetDefaultPrinterName causes a sandbox violation (see Bug
        // 1329216). The printer name is not needed anywhere else on Linux
        // before it gets to the parent. In the parent, we will then query the
        // default printer name if no name is set. Unless we are in the parent,
        // we will skip this part.
        if (!XRE_IsParentProcess()) {
          shouldGetDefaultPrinterName = false;
        }
#  endif
        if (shouldGetDefaultPrinterName) {
          printSettingsService->GetDefaultPrinterName(printerName);
          printSettings->SetPrinterName(printerName);
        }
        printSettingsService->InitPrintSettingsFromPrinter(printerName,
                                                           printSettings);
        printSettingsService->InitPrintSettingsFromPrefs(
            printSettings, true, nsIPrintSettings::kInitSaveAll);
      } else {
        printSettingsService->GetNewPrintSettings(
            getter_AddRefs(printSettings));
      }

      EnterModalState();
      webBrowserPrint->Print(printSettings, nullptr);
      LeaveModalState();

      bool savePrintSettings =
          Preferences::GetBool("print.save_print_settings", false);
      if (printSettingsAreGlobal && savePrintSettings) {
        printSettingsService->SavePrintSettingsToPrefs(
            printSettings, true, nsIPrintSettings::kInitSaveAll);
        printSettingsService->SavePrintSettingsToPrefs(
            printSettings, false, nsIPrintSettings::kInitSavePrinterName);
      }
    } else {
      webBrowserPrint->GetGlobalPrintSettings(getter_AddRefs(printSettings));
      webBrowserPrint->Print(printSettings, nullptr);
    }
  }
#endif  // NS_PRINTING
}

void nsGlobalWindowOuter::MoveToOuter(int32_t aXPos, int32_t aYPos,
                                      CallerType aCallerType,
                                      ErrorResult& aError) {
  /*
   * If caller is not chrome and the user has not explicitly exempted the site,
   * prevent window.moveTo() by exiting early
   */

  if (!CanMoveResizeWindows(aCallerType) || IsFrame()) {
    return;
  }

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

  nsCOMPtr<nsIScreenManager> screenMgr =
      do_GetService("@mozilla.org/gfx/screenmanager;1");
  nsCOMPtr<nsIScreen> screen;
  if (screenMgr) {
    CSSIntSize size;
    GetInnerSize(size);
    screenMgr->ScreenForRect(aXPos, aYPos, size.width, size.height,
                             getter_AddRefs(screen));
  }

  if (screen) {
    // On secondary displays, the "CSS px" coordinates are offset so that they
    // share their origin with global desktop pixels, to avoid ambiguities in
    // the coordinate space when there are displays with different DPIs.
    // (See the corresponding code in GetScreenXY() above.)
    int32_t screenLeftDeskPx, screenTopDeskPx, w, h;
    screen->GetRectDisplayPix(&screenLeftDeskPx, &screenTopDeskPx, &w, &h);
    CSSIntPoint cssPos(aXPos - screenLeftDeskPx, aYPos - screenTopDeskPx);
    CheckSecurityLeftAndTop(&cssPos.x, &cssPos.y, aCallerType);

    double scale;
    screen->GetDefaultCSSScaleFactor(&scale);
    LayoutDevicePoint devPos = cssPos * CSSToLayoutDeviceScale(scale);

    screen->GetContentsScaleFactor(&scale);
    DesktopPoint deskPos = devPos / DesktopToLayoutDeviceScale(scale);
    aError = treeOwnerAsWin->SetPositionDesktopPix(screenLeftDeskPx + deskPos.x,
                                                   screenTopDeskPx + deskPos.y);
  } else {
    // We couldn't find a screen? Just assume a 1:1 mapping.
    CSSIntPoint cssPos(aXPos, aXPos);
    CheckSecurityLeftAndTop(&cssPos.x, &cssPos.y, aCallerType);
    LayoutDevicePoint devPos = cssPos * CSSToLayoutDeviceScale(1.0);
    aError = treeOwnerAsWin->SetPosition(devPos.x, devPos.y);
  }

  CheckForDPIChange();
}

void nsGlobalWindowOuter::MoveByOuter(int32_t aXDif, int32_t aYDif,
                                      CallerType aCallerType,
                                      ErrorResult& aError) {
  /*
   * If caller is not chrome and the user has not explicitly exempted the site,
   * prevent window.moveBy() by exiting early
   */

  if (!CanMoveResizeWindows(aCallerType) || IsFrame()) {
    return;
  }

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

  // To do this correctly we have to convert what we get from GetPosition
  // into CSS pixels, add the arguments, do the security check, and
  // then convert back to device pixels for the call to SetPosition.

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

  // mild abuse of a "size" object so we don't need more helper functions
  nsIntSize cssPos(DevToCSSIntPixels(nsIntSize(x, y)));

  cssPos.width += aXDif;
  cssPos.height += aYDif;

  CheckSecurityLeftAndTop(&cssPos.width, &cssPos.height, aCallerType);

  nsIntSize newDevPos(CSSToDevIntPixels(cssPos));

  aError = treeOwnerAsWin->SetPosition(newDevPos.width, newDevPos.height);

  CheckForDPIChange();
}

nsresult nsGlobalWindowOuter::MoveBy(int32_t aXDif, int32_t aYDif) {
  ErrorResult rv;
  MoveByOuter(aXDif, aYDif, CallerType::System, rv);

  return rv.StealNSResult();
}

void nsGlobalWindowOuter::ResizeToOuter(int32_t aWidth, int32_t aHeight,
                                        CallerType aCallerType,
                                        ErrorResult& aError) {
  /*
   * If caller is a browser-element then dispatch a resize event to
   * the embedder.
   */
  if (mDocShell && mDocShell->GetIsMozBrowser()) {
    CSSIntSize size(aWidth, aHeight);
    if (!DispatchResizeEvent(size)) {
      // The embedder chose to prevent the default action for this
      // event, so let's not resize this window after all...
      return;
    }
  }

  /*
   * If caller is not chrome and the user has not explicitly exempted the site,
   * prevent window.resizeTo() by exiting early
   */

  if (!CanMoveResizeWindows(aCallerType) || IsFrame()) {
    return;
  }

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

  nsIntSize cssSize(aWidth, aHeight);
  CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerType);

  nsIntSize devSz(CSSToDevIntPixels(cssSize));

  aError = treeOwnerAsWin->SetSize(devSz.width, devSz.height, true);

  CheckForDPIChange();
}

void nsGlobalWindowOuter::ResizeByOuter(int32_t aWidthDif, int32_t aHeightDif,
                                        CallerType aCallerType,
                                        ErrorResult& aError) {
  /*
   * If caller is a browser-element then dispatch a resize event to
   * parent.
   */
  if (mDocShell && mDocShell->GetIsMozBrowser()) {
    CSSIntSize size;
    if (NS_FAILED(GetInnerSize(size))) {
      return;
    }

    size.width += aWidthDif;
    size.height += aHeightDif;

    if (!DispatchResizeEvent(size)) {
      // The embedder chose to prevent the default action for this
      // event, so let's not resize this window after all...
      return;
    }
  }

  /*
   * If caller is not chrome and the user has not explicitly exempted the site,
   * prevent window.resizeBy() by exiting early
   */

  if (!CanMoveResizeWindows(aCallerType) || IsFrame()) {
    return;
  }

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

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

  // To do this correctly we have to convert what we got from GetSize
  // into CSS pixels, add the arguments, do the security check, and
  // then convert back to device pixels for the call to SetSize.

  nsIntSize cssSize(DevToCSSIntPixels(nsIntSize(width, height)));

  cssSize.width += aWidthDif;
  cssSize.height += aHeightDif;

  CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerType);

  nsIntSize newDevSize(CSSToDevIntPixels(cssSize));

  aError = treeOwnerAsWin->SetSize(newDevSize.width, newDevSize.height, true);

  CheckForDPIChange();
}

void nsGlobalWindowOuter::SizeToContentOuter(CallerType aCallerType,
                                             ErrorResult& aError) {
  if (!mDocShell) {
    return;
  }

  /*
   * If caller is not chrome and the user has not explicitly exempted the site,
   * prevent window.sizeToContent() by exiting early
   */

  if (!CanMoveResizeWindows(aCallerType) || IsFrame()) {
    return;
  }

  // The content viewer does a check to make sure that it's a content
  // viewer for a toplevel docshell.
  nsCOMPtr<nsIContentViewer> cv;
  mDocShell->GetContentViewer(getter_AddRefs(cv));
  if (!cv) {
    aError.Throw(NS_ERROR_FAILURE);
    return;
  }

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

  // Make sure the new size is following the CheckSecurityWidthAndHeight
  // rules.
  nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
  if (!treeOwner) {
    aError.Throw(NS_ERROR_FAILURE);
    return;
  }

  nsIntSize cssSize(DevToCSSIntPixels(nsIntSize(width, height)));
  CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerType);

  nsIntSize newDevSize(CSSToDevIntPixels(cssSize));

  aError =
      treeOwner->SizeShellTo(mDocShell, newDevSize.width, newDevSize.height);
}

already_AddRefed<nsPIWindowRoot> nsGlobalWindowOuter::GetTopWindowRoot() {
  nsPIDOMWindowOuter* piWin = GetPrivateRoot();
  if (!piWin) {
    return nullptr;
  }

  nsCOMPtr<nsPIWindowRoot> window =
      do_QueryInterface(piWin->GetChromeEventHandler());
  return window.forget();
}

void nsGlobalWindowOuter::FirePopupBlockedEvent(
    Document* aDoc, nsIURI* aPopupURI, const nsAString& aPopupWindowName,
    const nsAString& aPopupWindowFeatures) {
  MOZ_ASSERT(aDoc);

  // Fire a "DOMPopupBlocked" event so that the UI can hear about
  // blocked popups.
  PopupBlockedEventInit init;
  init.mBubbles = true;
  init.mCancelable = true;
  // XXX: This is a different object, but webidl requires an inner window here
  // now.
  init.mRequestingWindow = GetCurrentInnerWindowInternal();
  init.mPopupWindowURI = aPopupURI;
  init.mPopupWindowName = aPopupWindowName;
  init.mPopupWindowFeatures = aPopupWindowFeatures;

  RefPtr<PopupBlockedEvent> event = PopupBlockedEvent::Constructor(
      aDoc, NS_LITERAL_STRING("DOMPopupBlocked"), init);

  event->SetTrusted(true);

  aDoc->DispatchEvent(*event);
}

void nsGlobalWindowOuter::NotifyContentBlockingEvent(unsigned aEvent,
                                                     nsIChannel* aChannel,
                                                     bool aBlocked,
                                                     nsIURI* aURIHint) {
  MOZ_ASSERT(aURIHint);

  nsCOMPtr<nsIDocShell> docShell = GetDocShell();
  if (!docShell) {
    return;
  }
  nsCOMPtr<Document> doc = docShell->GetDocument();
  NS_ENSURE_TRUE_VOID(doc);

  nsCOMPtr<nsIURI> uri(aURIHint);
  nsCOMPtr<nsIChannel> channel(aChannel);

  static bool prefInitialized = false;
  if (!prefInitialized) {
    Preferences::AddBoolVarCache(
        &gSyncContentBlockingNotifications,
        "dom.testing.sync-content-blocking-notifications", false);
    prefInitialized = true;
  }

  nsCOMPtr<nsIRunnable> func = NS_NewRunnableFunction(
      "NotifyContentBlockingEventDelayed",
      [doc, docShell, uri, channel, aEvent, aBlocked]() {
        // This event might come after the user has navigated to another
        // page. To prevent showing the TrackingProtection UI on the wrong
        // page, we need to check that the loading URI for the channel is
        // the same as the URI currently loaded in the document.
        if (!SameLoadingURI(doc, channel) &&
            aEvent == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
          return;
        }

        // Notify nsIWebProgressListeners of this content blocking event.
        // Can be used to change the UI state.
        uint32_t event = 0;
        nsCOMPtr<nsISecureBrowserUI> securityUI;
        docShell->GetSecurityUI(getter_AddRefs(securityUI));
        if (!securityUI) {
          return;
        }
        securityUI->GetContentBlockingEvent(&event);
        nsAutoCString origin;
        nsContentUtils::GetASCIIOrigin(uri, origin);

        bool blockedValue = aBlocked;
        bool unblocked = false;
        if (aEvent == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
          doc->SetHasTrackingContentBlocked(aBlocked, origin);
          if (!aBlocked) {
            unblocked = !doc->GetHasTrackingContentBlocked();
          }
        } else if (aEvent ==
                   nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT) {
          doc->SetHasTrackingContentLoaded(aBlocked, origin);
          if (!aBlocked) {
            unblocked = !doc->GetHasTrackingContentLoaded();
          }
        } else if (aEvent == nsIWebProgressListener::
                                 STATE_BLOCKED_FINGERPRINTING_CONTENT) {
          doc->SetHasFingerprintingContentBlocked(aBlocked, origin);
          if (!aBlocked) {
            unblocked = !doc->GetHasFingerprintingContentBlocked();
          }
        } else if (aEvent == nsIWebProgressListener::
                                 STATE_LOADED_FINGERPRINTING_CONTENT) {
          doc->SetHasFingerprintingContentLoaded(aBlocked, origin);
          if (!aBlocked) {
            unblocked = !doc->GetHasFingerprintingContentLoaded();
          }
        } else if (aEvent ==
                   nsIWebProgressListener::STATE_BLOCKED_CRYPTOMINING_CONTENT) {
          doc->SetHasCryptominingContentBlocked(aBlocked, origin);
          if (!aBlocked) {
            unblocked = !doc->GetHasCryptominingContentBlocked();
          }
        } else if (aEvent ==
                   nsIWebProgressListener::STATE_LOADED_CRYPTOMINING_CONTENT) {
          doc->SetHasCryptominingContentLoaded(aBlocked, origin);
          if (!aBlocked) {
            unblocked = !doc->GetHasCryptominingContentLoaded();
          }
        } else if (aEvent == nsIWebProgressListener::
                                 STATE_COOKIES_BLOCKED_BY_PERMISSION) {
          doc->SetHasCookiesBlockedByPermission(aBlocked, origin);
          if (!aBlocked) {
            unblocked = !doc->GetHasCookiesBlockedByPermission();
          }
        } else if (aEvent ==
                   nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) {
          doc->SetHasTrackingCookiesBlocked(aBlocked, origin);
          if (!aBlocked) {
            unblocked = !doc->GetHasTrackingCookiesBlocked();
          }
        } else if (aEvent ==
                   nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL) {
          doc->SetHasAllCookiesBlocked(aBlocked, origin);
          if (!aBlocked) {
            unblocked = !doc->GetHasAllCookiesBlocked();
          }
        } else if (aEvent ==
                   nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN) {
          doc->SetHasForeignCookiesBlocked(aBlocked, origin);
          if (!aBlocked) {
            unblocked = !doc->GetHasForeignCookiesBlocked();
          }
        } else if (aEvent == nsIWebProgressListener::STATE_COOKIES_LOADED) {
          MOZ_ASSERT(!aBlocked,
                     "We don't expected to see blocked STATE_COOKIES_LOADED");
          // Note that the logic in this branch is the logical negation of
          // the logic in other branches, since the Document API we have is
          // phrased in "loaded" terms as opposed to "blocked" terms.
          blockedValue = !aBlocked;
          doc->SetHasCookiesLoaded(blockedValue, origin);
          if (!aBlocked) {
            unblocked = !doc->GetHasCookiesLoaded();
          }
        } else {
          // Ignore nsIWebProgressListener::STATE_BLOCKED_UNSAFE_CONTENT;
        }
        const uint32_t oldEvent = event;
        if (blockedValue) {
          event |= aEvent;
        } else if (unblocked) {
          event &= ~aEvent;
        }

        if (event == oldEvent
#ifdef ANDROID
            // GeckoView always needs to notify about blocked trackers,
            // since the GeckoView API always needs to report the URI and
            // type of any blocked tracker. We use a platform-dependent code
            // path here because reporting this notification on desktop
            // platforms isn't necessary and doing so can have a big
            // performance cost.
            && aEvent != nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT
#endif
        ) {
          // Avoid dispatching repeated notifications when nothing has
          // changed
          return;
        }

        nsDocShell::Cast(docShell)->nsDocLoader::OnContentBlockingEvent(channel,
                                                                        event);
      });
  nsresult rv;
  if (gSyncContentBlockingNotifications) {
    rv = func->Run();
  } else {
    rv = NS_DispatchToCurrentThreadQueue(func.forget(), 100,
                                         EventQueuePriority::Idle);
  }
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }
}

// static
bool nsGlobalWindowOuter::SameLoadingURI(Document* aDoc, nsIChannel* aChannel) {
  nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
  nsCOMPtr<nsILoadInfo> channelLoadInfo = aChannel->GetLoadInfo();
  if (!channelLoadInfo || !docURI) {
    return false;
  }

  nsCOMPtr<nsIPrincipal> channelLoadingPrincipal =
      channelLoadInfo->LoadingPrincipal();
  if (!channelLoadingPrincipal) {
    // TYPE_DOCUMENT loads will not have a channelLoadingPrincipal. But top
    // level loads should not be blocked by Tracking Protection, so we will
    // return false
    return false;
  }
  nsCOMPtr<nsIURI> channelLoadingURI;
  channelLoadingPrincipal->GetURI(getter_AddRefs(channelLoadingURI));
  if (!channelLoadingURI) {
    return false;
  }
  bool equals = false;
  nsresult rv = docURI->EqualsExceptRef(channelLoadingURI, &equals);
  return NS_SUCCEEDED(rv) && equals;
}

// static
bool nsGlobalWindowOuter::CanSetProperty(const char* aPrefName) {
  // Chrome can set any property.
  if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
    return true;
  }

  // If the pref is set to true, we can not set the property
  // and vice versa.
  return !Preferences::GetBool(aPrefName, true);
}

bool nsGlobalWindowOuter::PopupWhitelisted() {
  if (mDoc && PopupBlocker::CanShowPopupByPermission(mDoc->NodePrincipal())) {
    return true;
  }

  nsCOMPtr<nsPIDOMWindowOuter> parent = GetParent();
  if (parent == this) {
    return false;
  }

  return nsGlobalWindowOuter::Cast(parent)->PopupWhitelisted();
}

/*
 * Examine the current document state to see if we're in a way that is
 * typically abused by web designers. The window.open code uses this
 * routine to determine whether to allow the new window.
 * Returns a value from the PopupControlState enum.
 */
PopupBlocker::PopupControlState nsGlobalWindowOuter::RevisePopupAbuseLevel(
    PopupBlocker::PopupControlState aControl) {
  NS_ASSERTION(mDocShell, "Must have docshell");

  if (mDocShell->ItemType() != nsIDocShellTreeItem::typeContent) {
    return PopupBlocker::openAllowed;
  }

  PopupBlocker::PopupControlState abuse = aControl;
  switch (abuse) {
    case PopupBlocker::openControlled:
    case PopupBlocker::openBlocked:
    case PopupBlocker::openOverridden:
      if (PopupWhitelisted())
        abuse = PopupBlocker::PopupControlState(abuse - 1);
      break;
    case PopupBlocker::openAbused:
      if (PopupWhitelisted())
        // Skip PopupBlocker::openBlocked
        abuse = PopupBlocker::openControlled;
      break;
    case PopupBlocker::openAllowed:
      break;
    default:
      NS_WARNING("Strange PopupControlState!");
  }

  // limit the number of simultaneously open popups
  if (abuse == PopupBlocker::openAbused || abuse == PopupBlocker::openBlocked ||
      abuse == PopupBlocker::openControlled) {
    int32_t popupMax = Preferences::GetInt("dom.popup_maximum", -1);
    if (popupMax >= 0 && gOpenPopupSpamCount >= popupMax)
      abuse = PopupBlocker::openOverridden;
  }

  // If this popup is allowed, let's block any other for this event, forcing
  // PopupBlocker::openBlocked state.
  if ((abuse == PopupBlocker::openAllowed ||
       abuse == PopupBlocker::openControlled) &&
      StaticPrefs::dom_block_multiple_popups() && !PopupWhitelisted() &&
      !PopupBlocker::TryUsePopupOpeningToken()) {
    abuse = PopupBlocker::openBlocked;
  }

  return abuse;
}

/* If a window open is blocked, fire the appropriate DOM events. */
void nsGlobalWindowOuter::FireAbuseEvents(
    const nsAString& aPopupURL, const nsAString& aPopupWindowName,
    const nsAString& aPopupWindowFeatures) {
  // fetch the URI of the window requesting the opened window

  nsCOMPtr<nsPIDOMWindowOuter> window = GetTop();
  if (!window) {
    return;
  }

  nsCOMPtr<Document> topDoc = window->GetDoc();
  nsCOMPtr<nsIURI> popupURI;

  // build the URI of the would-have-been popup window
  // (see nsWindowWatcher::URIfromURL)

  // first, fetch the opener's base URI

  nsIURI* baseURL = nullptr;

  nsCOMPtr<Document> doc = GetEntryDocument();
  if (doc) baseURL = doc->GetDocBaseURI();

  // use the base URI to build what would have been the popup's URI
  nsCOMPtr<nsIIOService> ios(do_GetService(NS_IOSERVICE_CONTRACTID));
  if (ios)
    ios->NewURI(NS_ConvertUTF16toUTF8(aPopupURL), nullptr, baseURL,
                getter_AddRefs(popupURI));

  // fire an event block full of informative URIs
  FirePopupBlockedEvent(topDoc, popupURI, aPopupWindowName,
                        aPopupWindowFeatures);
}

Nullable<WindowProxyHolder> nsGlobalWindowOuter::OpenOuter(
    const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions,
    ErrorResult& aError) {
  nsCOMPtr<nsPIDOMWindowOuter> window;
  aError = OpenJS(aUrl, aName, aOptions, getter_AddRefs(window));
  RefPtr<BrowsingContext> bc;
  if (!window || !(bc = window->GetBrowsingContext())) {
    return nullptr;
  }
  return WindowProxyHolder(bc.forget());
}

nsresult nsGlobalWindowOuter::Open(const nsAString& aUrl,
                                   const nsAString& aName,
                                   const nsAString& aOptions,
                                   nsDocShellLoadState* aLoadState,
                                   bool aForceNoOpener,
                                   nsPIDOMWindowOuter** _retval) {
  return OpenInternal(aUrl, aName, aOptions,
                      false,             // aDialog
                      false,             // aContentModal
                      true,              // aCalledNoScript
                      false,             // aDoJSFixups
                      true,              // aNavigate
                      nullptr, nullptr,  // No args
                      aLoadState, aForceNoOpener, _retval);
}

nsresult nsGlobalWindowOuter::OpenJS(const nsAString& aUrl,
                                     const nsAString& aName,
                                     const nsAString& aOptions,
                                     nsPIDOMWindowOuter** _retval) {
  return OpenInternal(aUrl, aName, aOptions,
                      false,             // aDialog
                      false,             // aContentModal
                      false,             // aCalledNoScript
                      true,              // aDoJSFixups
                      true,              // aNavigate
                      nullptr, nullptr,  // No args
                      nullptr,           // aLoadState
                      false,             // aForceNoOpener
                      _retval);
}

// like Open, but attaches to the new window any extra parameters past
// [features] as a JS property named "arguments"
nsresult nsGlobalWindowOuter::OpenDialog(const nsAString& aUrl,
                                         const nsAString& aName,
                                         const nsAString& aOptions,
                                         nsISupports* aExtraArgument,
                                         nsPIDOMWindowOuter** _retval) {
  return OpenInternal(aUrl, aName, aOptions,
                      true,                     // aDialog
                      false,                    // aContentModal
                      true,                     // aCalledNoScript
                      false,                    // aDoJSFixups
                      true,                     // aNavigate
                      nullptr, aExtraArgument,  // Arguments
                      nullptr,                  // aLoadState
                      false,                    // aForceNoOpener
                      _retval);
}

// Like Open, but passes aNavigate=false.
/* virtual */ nsresult nsGlobalWindowOuter::OpenNoNavigate(
    const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions,
    nsPIDOMWindowOuter** _retval) {
  return OpenInternal(aUrl, aName, aOptions,
                      false,             // aDialog
                      false,             // aContentModal
                      true,              // aCalledNoScript
                      false,             // aDoJSFixups
                      false,             // aNavigate
                      nullptr, nullptr,  // No args
                      nullptr,           // aLoadState
                      false,             // aForceNoOpener
                      _retval);
}

Nullable<WindowProxyHolder> nsGlobalWindowOuter::OpenDialogOuter(
    JSContext* aCx, const nsAString& aUrl, const nsAString& aName,
    const nsAString& aOptions, const Sequence<JS::Value>& aExtraArgument,
    ErrorResult& aError) {
  nsCOMPtr<nsIJSArgArray> argvArray;
  aError =
      NS_CreateJSArgv(aCx, aExtraArgument.Length(), aExtraArgument.Elements(),
                      getter_AddRefs(argvArray));
  if (aError.Failed()) {
    return nullptr;
  }

  nsCOMPtr<nsPIDOMWindowOuter> dialog;
  aError = OpenInternal(aUrl, aName, aOptions,
                        true,                // aDialog
                        false,               // aContentModal
                        false,               // aCalledNoScript
                        false,               // aDoJSFixups
                        true,                // aNavigate
                        argvArray, nullptr,  // Arguments
                        nullptr,             // aLoadState
                        false,               // aForceNoOpener
                        getter_AddRefs(dialog));
  RefPtr<BrowsingContext> bc;
  if (!dialog || !(bc = dialog->GetBrowsingContext())) {
    return nullptr;
  }
  return WindowProxyHolder(bc.forget());
}

BrowsingContext* nsGlobalWindowOuter::GetFramesOuter() {
  RefPtr<nsPIDOMWindowOuter> frames(this);
  FlushPendingNotifications(FlushType::ContentAndNotify);
  return mBrowsingContext;
}

/* static */
nsGlobalWindowInner* nsGlobalWindowOuter::CallerInnerWindow(JSContext* aCx) {
  nsIGlobalObject* global = GetIncumbentGlobal();
  NS_ENSURE_TRUE(global, nullptr);
  JS::Rooted<JSObject*> scope(aCx, 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(aCx, scope);
    JS::Rooted<JSObject*> scopeProto(aCx);
    bool ok = JS_GetPrototype(aCx, scope, &scopeProto);
    NS_ENSURE_TRUE(ok, nullptr);
    if (scopeProto && xpc::IsSandboxPrototypeProxy(scopeProto) &&
        // Our current Realm on aCx is the sandbox.  Using that for the
        // CheckedUnwrapDynamic call makes sense: if the sandbox can unwrap the
        // window, we can use it.  And we do want CheckedUnwrapDynamic, because
        // the whole point is to unwrap windows.
        (scopeProto = js::CheckedUnwrapDynamic(
             scopeProto, aCx, /* 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);
}

/* static */
bool nsGlobalWindowOuter::GatherPostMessageData(
    JSContext* aCx, const nsAString& aTargetOrigin, BrowsingContext** aSource,
    nsAString& aOrigin, nsIURI** aTargetOriginURI,
    nsIPrincipal** aCallerPrincipal, nsGlobalWindowInner** aCallerInnerWindow,
    nsIURI** aCallerDocumentURI, ErrorResult& aError) {
  //
  // Window.postMessage is an intentional subversion of the same-origin policy.
  // As such, this code must be particularly careful in the information it
  // exposes to calling code.
  //
  // http://www.whatwg.org/specs/web-apps/current-work/multipage/section-crossDocumentMessages.html
  //

  // First, get the caller's window
  RefPtr<nsGlobalWindowInner> callerInnerWin = CallerInnerWindow(aCx);
  nsIPrincipal* callerPrin;
  if (callerInnerWin) {
    RefPtr<Document> doc = callerInnerWin->GetExtantDoc();
    if (!doc) {
      return false;
    }
    NS_IF_ADDREF(*aCallerDocumentURI = doc->GetDocumentURI());

    // Compute the caller's origin either from its principal or, in the case the
    // principal doesn't carry a URI (e.g. the system principal), the caller's
    // document.  We must get this now instead of when the event is created and
    // dispatched, because ultimately it is the identity of the calling window
    // *now* that determines who sent the message (and not an identity which
    // might have changed due to intervening navigations).
    callerPrin = callerInnerWin->GetPrincipal();
  } else {
    // In case the global is not a window, it can be a sandbox, and the
    // sandbox's principal can be used for the security check.
    nsIGlobalObject* global = GetIncumbentGlobal();
    NS_ASSERTION(global, "Why is there no global object?");
    callerPrin = global->PrincipalOrNull();
  }
  if (!callerPrin) {
    return false;
  }

  nsCOMPtr<nsIURI> callerOuterURI;
  if (NS_FAILED(callerPrin->GetURI(getter_AddRefs(callerOuterURI)))) {
    return false;
  }

  if (callerOuterURI) {
    // if the principal has a URI, use that to generate the origin
    nsContentUtils::GetUTFOrigin(callerPrin, aOrigin);
  } else if (callerInnerWin) {
    if (!*aCallerDocumentURI) {
      return false;
    }
    // otherwise use the URI of the document to generate origin
    nsContentUtils::GetUTFOrigin(*aCallerDocumentURI, aOrigin);
  } else {
    // in case of a sandbox with a system principal origin can be empty
    if (!nsContentUtils::IsSystemPrincipal(callerPrin)) {
      return false;
    }
  }
  NS_IF_ADDREF(*aCallerPrincipal = callerPrin);

  // "/" indicates same origin as caller, "*" indicates no specific origin is
  // required.
  if (!aTargetOrigin.EqualsASCII("/") && !aTargetOrigin.EqualsASCII("*")) {
    nsCOMPtr<nsIURI> targetOriginURI;
    if (NS_FAILED(NS_NewURI(getter_AddRefs(targetOriginURI), aTargetOrigin))) {
      aError.Throw(NS_ERROR_DOM_SYNTAX_ERR);
      return false;
    }

    nsresult rv = NS_MutateURI(targetOriginURI)
                      .SetUserPass(EmptyCString())
                      .SetPathQueryRef(EmptyCString())
                      .Finalize(aTargetOriginURI);
    if (NS_FAILED(rv)) {
      return false;
    }
  }

  if (!nsContentUtils::IsCallerChrome() && callerInnerWin &&
      callerInnerWin->GetOuterWindowInternal()) {
    NS_ADDREF(*aSource = callerInnerWin->GetOuterWindowInternal()
                             ->GetBrowsingContext());
  } else {
    *aSource = nullptr;
  }

  callerInnerWin.forget(aCallerInnerWindow);

  return true;
}

bool nsGlobalWindowOuter::GetPrincipalForPostMessage(
    const nsAString& aTargetOrigin, nsIURI* aTargetOriginURI,
    nsIPrincipal* aCallerPrincipal, nsIPrincipal& aSubjectPrincipal,
    nsIPrincipal** aProvidedPrincipal) {
  //
  // Window.postMessage is an intentional subversion of the same-origin policy.
  // As such, this code must be particularly careful in the information it
  // exposes to calling code.
  //
  // http://www.whatwg.org/specs/web-apps/current-work/multipage/section-crossDocumentMessages.html
  //

  // Convert the provided origin string into a URI for comparison purposes.
  nsCOMPtr<nsIPrincipal> providedPrincipal;

  if (aTargetOrigin.EqualsASCII("/")) {
    providedPrincipal = aCallerPrincipal;
  }
  // "*" indicates no specific origin is required.
  else if (!aTargetOrigin.EqualsASCII("*")) {
    OriginAttributes attrs = aSubjectPrincipal.OriginAttributesRef();
    if (aSubjectPrincipal.IsSystemPrincipal()) {
      auto principal = BasePrincipal::Cast(GetPrincipal());

      if (attrs != principal->OriginAttributesRef()) {
        nsCOMPtr<nsIURI> targetURI;
        nsAutoCString targetURL;
        nsAutoCString sourceOrigin;
        nsAutoCString targetOrigin;

        if (NS_FAILED(principal->GetURI(getter_AddRefs(targetURI))) ||
            NS_FAILED(targetURI->GetAsciiSpec(targetURL)) ||
            NS_FAILED(principal->GetOrigin(targetOrigin)) ||
            NS_FAILED(aSubjectPrincipal.GetOrigin(sourceOrigin))) {
          NS_WARNING("Failed to get source and target origins");
          return false;
        }

        nsContentUtils::LogSimpleConsoleError(
            NS_ConvertUTF8toUTF16(nsPrintfCString(
                R"(Attempting to post a message to window with url "%s" and )"
                R"(origin "%s" from a system principal scope with mismatched )"
                R"(origin "%s".)",
                targetURL.get(), targetOrigin.get(), sourceOrigin.get())),
            "DOM", !!principal->PrivateBrowsingId());

        attrs = principal->OriginAttributesRef();
      }
    }

    // Create a nsIPrincipal inheriting the app/browser attributes from the
    // caller.
    providedPrincipal =
        BasePrincipal::CreateCodebasePrincipal(aTargetOriginURI, attrs);
    if (NS_WARN_IF(!providedPrincipal)) {
      return false;
    }
  } else {
    // We still need to check the originAttributes if the target origin is '*'.
    // But we will ingore the FPD here since the FPDs are possible to be
    // different.
    auto principal = BasePrincipal::Cast(GetPrincipal());
    NS_ENSURE_TRUE(principal, false);

    OriginAttributes targetAttrs = principal->OriginAttributesRef();
    OriginAttributes sourceAttrs = aSubjectPrincipal.OriginAttributesRef();
    // We have to exempt the check of OA if the subject prioncipal is a system
    // principal since there are many tests try to post messages to content from
    // chrome with a mismatch OA. For example, using the ContentTask.spawn() to
    // post a message into a private browsing window. The injected code in
    // ContentTask.spawn() will be executed under the system principal and the
    // OA of the system principal mismatches with the OA of a private browsing
    // window.
    MOZ_DIAGNOSTIC_ASSERT(aSubjectPrincipal.IsSystemPrincipal() ||
                          sourceAttrs.EqualsIgnoringFPD(targetAttrs));

    // If 'privacy.firstparty.isolate.block_post_message' is true, we will block
    // postMessage across different first party domains.
    if (OriginAttributes::IsBlockPostMessageForFPI() &&
        !aSubjectPrincipal.IsSystemPrincipal() &&
        sourceAttrs.mFirstPartyDomain != targetAttrs.mFirstPartyDomain) {
      return false;
    }
  }

  providedPrincipal.forget(aProvidedPrincipal);
  return true;
}

void nsGlobalWindowOuter::PostMessageMozOuter(JSContext* aCx,
                                              JS::Handle<JS::Value> aMessage,
                                              const nsAString& aTargetOrigin,
                                              JS::Handle<JS::Value> aTransfer,
                                              nsIPrincipal& aSubjectPrincipal,
                                              ErrorResult& aError) {
  RefPtr<BrowsingContext> sourceBc;
  nsAutoString origin;
  nsCOMPtr<nsIURI> targetOriginURI;
  nsCOMPtr<nsIPrincipal> callerPrincipal;
  RefPtr<nsGlobalWindowInner> callerInnerWindow;
  nsCOMPtr<nsIURI> callerDocumentURI;
  if (!GatherPostMessageData(aCx, aTargetOrigin, getter_AddRefs(sourceBc),
                             origin, getter_AddRefs(targetOriginURI),
                             getter_AddRefs(callerPrincipal),
                             getter_AddRefs(callerInnerWindow),
                             getter_AddRefs(callerDocumentURI), aError)) {
    return;
  }

  nsCOMPtr<nsIPrincipal> providedPrincipal;
  if (!GetPrincipalForPostMessage(aTargetOrigin, targetOriginURI,
                                  callerPrincipal, aSubjectPrincipal,
                                  getter_AddRefs(providedPrincipal))) {
    return;
  }

  // Create and asynchronously dispatch a runnable which will handle actual DOM
  // event creation and dispatch.
  RefPtr<PostMessageEvent> event = new PostMessageEvent(
      sourceBc, origin, this, providedPrincipal,
      callerInnerWindow ? callerInnerWindow->WindowID() : 0, callerDocumentURI);

  event->Write(aCx, aMessage, aTransfer, aError);
  if (NS_WARN_IF(aError.Failed())) {
    return;
  }

  aError = Dispatch(TaskCategory::Other, event.forget());
}

class nsCloseEvent : public Runnable {
  RefPtr<nsGlobalWindowOuter> mWindow;
  bool mIndirect;

  nsCloseEvent(nsGlobalWindowOuter* aWindow, bool aIndirect)
      : mozilla::Runnable("nsCloseEvent"),
        mWindow(aWindow),
        mIndirect(aIndirect) {}

 public:
  static nsresult PostCloseEvent(nsGlobalWindowOuter* aWindow, bool aIndirect) {
    nsCOMPtr<nsIRunnable> ev = new nsCloseEvent(aWindow, aIndirect);
    nsresult rv = aWindow->Dispatch(TaskCategory::Other, ev.forget());
    if (NS_SUCCEEDED(rv)) aWindow->MaybeForgiveSpamCount();
    return rv;
  }

  NS_IMETHOD Run() override {
    if (mWindow) {
      if (mIndirect) {
        return PostCloseEvent(mWindow, false);
      }
      mWindow->ReallyCloseWindow();
    }
    return NS_OK;
  }
};

bool nsGlobalWindowOuter::CanClose() {
  if (mIsChrome) {
    nsCOMPtr<nsIBrowserDOMWindow> bwin;
    GetBrowserDOMWindow(getter_AddRefs(bwin));

    bool canClose = true;
    if (bwin && NS_SUCCEEDED(bwin->CanClose(&canClose))) {
      return canClose;
    }
  }

  if (!mDocShell) {
    return true;
  }

  // Ask the content viewer whether the toplevel window can close.
  // If the content viewer returns false, it is responsible for calling
  // Close() as soon as it is possible for the window to close.
  // This allows us to not close the window while printing is happening.

  nsCOMPtr<nsIContentViewer> cv;
  mDocShell->GetContentViewer(getter_AddRefs(cv));
  if (cv) {
    bool canClose;
    nsresult rv = cv->PermitUnload(&canClose);
    if (NS_SUCCEEDED(rv) && !canClose) return false;

    rv = cv->RequestWindowClose(&canClose);
    if (NS_SUCCEEDED(rv) && !canClose) return false;
  }

  return true;
}

void nsGlobalWindowOuter::CloseOuter(bool aTrustedCaller) {
  if (!mDocShell || IsInModalState() ||
      (IsFrame() && !mDocShell->GetIsMozBrowser())) {
    // window.close() is called on a frame in a frameset, on a window
    // that's already closed, or on a window for which there's
    // currently a modal dialog open. Ignore such calls.
    return;
  }

  if (mHavePendingClose) {
    // We're going to be closed anyway; do nothing since we don't want
    // to double-close
    return;
  }

  if (mBlockScriptedClosingFlag) {
    // A script's popup has been blocked and we don't want
    // the window to be closed directly after this event,
    // so the user can see that there was a blocked popup.
    return;
  }

  // Don't allow scripts from content to close non-neterror windows that
  // were not opened by script.
  if (mDoc) {
    nsAutoString url;
    nsresult rv = mDoc->GetURL(url);
    NS_ENSURE_SUCCESS_VOID(rv);

    if (!StringBeginsWith(url, NS_LITERAL_STRING("about:neterror")) &&
        !mHadOriginalOpener && !aTrustedCaller) {
      bool allowClose =
          mAllowScriptsToClose ||
          Preferences::GetBool("dom.allow_scripts_to_close_windows", true);
      if (!allowClose) {
        // We're blocking the close operation
        // report localized error msg in JS console
        nsContentUtils::ReportToConsole(
            nsIScriptError::warningFlag, NS_LITERAL_CSTRING("DOM Window"),
            mDoc,  // Better name for the category?
            nsContentUtils::eDOM_PROPERTIES, "WindowCloseBlockedWarning");

        return;
      }
    }
  }

  if (!mInClose && !mIsClosed && !CanClose()) {
    return;
  }

  // Fire a DOM event notifying listeners that this window is about to
  // be closed. The tab UI code may choose to cancel the default
  // action for this event, if so, we won't actually close the window
  // (since the tab UI code will close the tab in stead). Sure, this
  // could be abused by content code, but do we care? I don't think
  // so...

  bool wasInClose = mInClose;
  mInClose = true;

  if (!DispatchCustomEvent(NS_LITERAL_STRING("DOMWindowClose"))) {
    // Someone chose to prevent the default action for this event, if
    // so, let's not close this window after all...

    mInClose = wasInClose;
    return;
  }

  FinalClose();
}

nsresult nsGlobalWindowOuter::Close() {
  CloseOuter(/* aTrustedCaller = */ true);
  return NS_OK;
}

void nsGlobalWindowOuter::ForceClose() {
  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);

  if (IsFrame() || !mDocShell) {
    // This may be a frame in a frameset, or a window that's already closed.
    // Ignore such calls.
    return;
  }

  if (mHavePendingClose) {
    // We're going to be closed anyway; do nothing since we don't want
    // to double-close
    return;
  }

  mInClose = true;

  DispatchCustomEvent(NS_LITERAL_STRING("DOMWindowClose"));

  FinalClose();
}

void nsGlobalWindowOuter::FinalClose() {
  // Flag that we were closed.
  mIsClosed = true;

  // If we get here from CloseOuter then it means that the parent process is
  // going to close our window for us. It's just important to set mIsClosed.
  if (XRE_GetProcessType() == GeckoProcessType_Content) {
    return;
  }

  // This stuff is non-sensical but incredibly fragile. The reasons for the
  // behavior here don't make sense today and may not have ever made sense,
  // but various bits of frontend code break when you change them. If you need
  // to fix up this behavior, feel free to. It's a righteous task, but involves
  // wrestling with various download manager tests, frontend code, and possible
  // broken addons. The chrome tests in toolkit/mozapps/downloads are a good
  // testing ground.
  //
  // In particular, if some inner of |win| is the entry global, we must
  // complete _two_ round-trips to the event loop before the call to
  // ReallyCloseWindow. This allows setTimeout handlers that are set after
  // FinalClose() is called to run before the window is torn down.
  nsCOMPtr<nsPIDOMWindowInner> entryWindow =
      do_QueryInterface(GetEntryGlobal());
  bool indirect = entryWindow && entryWindow->GetOuterWindow() == this;
  if (NS_FAILED(nsCloseEvent::PostCloseEvent(this, indirect))) {
    ReallyCloseWindow();
  } else {
    mHavePendingClose = true;
  }
}

void nsGlobalWindowOuter::ReallyCloseWindow() {
  // Make sure we never reenter this method.
  mHavePendingClose = true;

  nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();

  // If there's no treeOwnerAsWin, this window must already be closed.

  if (treeOwnerAsWin) {
    // but if we're a browser window we could be in some nasty
    // self-destroying cascade that we should mostly ignore

    if (mDocShell) {
      nsCOMPtr<nsIBrowserDOMWindow> bwin;
      nsCOMPtr<nsIDocShellTreeItem> rootItem;
      mDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
      nsCOMPtr<nsPIDOMWindowOuter> rootWin =
          rootItem ? rootItem->GetWindow() : nullptr;
      nsCOMPtr<nsIDOMChromeWindow> chromeWin(do_QueryInterface(rootWin));
      if (chromeWin) chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin));

      if (rootWin) {
        /* Normally we destroy the entire window, but not if