/* 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 "mozilla/dom/WindowGlobalChild.h"

#include "GeckoProfiler.h"
#include "Navigator.h"
#include "Units.h"
#include "gfxPlatform.h"
#include "mozilla/AntiTrackingUtils.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/MozPrintCallbackRunner.h"
#include "mozilla/PresShell.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/dom/AudioSession.h"
#include "mozilla/dom/BrowserBridgeChild.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/BrowsingContextGroup.h"
#include "mozilla/dom/CloseWatcherManager.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/dom/IdentityCredential.h"
#include "mozilla/dom/InProcessChild.h"
#include "mozilla/dom/InProcessParent.h"
#include "mozilla/dom/JSActorService.h"
#include "mozilla/dom/JSWindowActorBinding.h"
#include "mozilla/dom/JSWindowActorChild.h"
#include "mozilla/dom/ModelContext.h"
#include "mozilla/dom/MozFrameLoaderOwnerBinding.h"
#include "mozilla/dom/PolicyContainer.h"
#include "mozilla/dom/Promise-inl.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ReportingUtils.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/SecurityPolicyViolationEvent.h"
#include "mozilla/dom/SessionStoreRestoreData.h"
#include "mozilla/dom/WindowContext.h"
#include "mozilla/dom/WindowGlobalActorsBinding.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/CrossProcessPaint.h"
#include "mozilla/ipc/Endpoint.h"
#include "nsAtom.h"
#include "nsContentUtils.h"
#include "nsDocShell.h"
#include "nsFocusManager.h"
#include "nsFrameLoader.h"
#include "nsFrameLoaderOwner.h"
#include "nsGlobalWindowInner.h"
#include "nsIDocumentEncoder.h"
#include "nsIHttpChannelInternal.h"
#include "nsITimer.h"
#include "nsIURIMutator.h"
#include "nsNetUtil.h"
#include "nsQueryObject.h"
#include "nsScriptSecurityManager.h"
#include "nsSerializationHelper.h"
#include "nsURLHelper.h"
#include "xpcpublic.h"

using namespace mozilla::ipc;
using namespace mozilla::dom::ipc;

namespace mozilla::dom {

// Retrieves language metadata for the current document.
//
// The request waits for the document to load up to a configured limit. Once
// collection begins, it may retry until the visible text sample meets the
// requested minimum or the configured retry limit is reached. The request
// completes without metadata if the current document is no longer eligible for
// collection.
class WindowGlobalChild::DocumentLanguageMetadataRequest final {
 public:
  NS_INLINE_DECL_REFCOUNTING(DocumentLanguageMetadataRequest)

  DocumentLanguageMetadataRequest(
      WindowGlobalChild* aWindowGlobalChild, uint32_t aTextSampleMinCodeUnits,
      uint32_t aTextSampleTargetCodeUnits,
      RequestDocumentLanguageMetadataResolver&& aResolver)
      : mWindowGlobalChild(aWindowGlobalChild),
        mResolver(std::move(aResolver)),
        mTextSampleMinCodeUnits(aTextSampleMinCodeUnits),
        mTextSampleTargetCodeUnits(aTextSampleTargetCodeUnits) {
    MOZ_ASSERT(aWindowGlobalChild);
    MOZ_ASSERT(aTextSampleMinCodeUnits <= aTextSampleTargetCodeUnits);
  }

  // Starts the process of collecting language metadata from the document
  // by waiting for the document to load until the configured timeout, or
  // by collecting immediately if the document has already loaded.
  void Start() {
    RefPtr<WindowGlobalChild> windowGlobalChild = mWindowGlobalChild.get();
    if (!windowGlobalChild ||
        !windowGlobalChild->CanCollectDocumentLanguageMetadata()) {
      Cancel();
      return;
    }

    if (windowGlobalChild->GetWindowGlobal()->IsDocumentLoaded()) {
      OnDocumentLoaded();
      return;
    }

    uint32_t loadTimeoutMs =
        StaticPrefs::dom_document_language_metadata_load_timeout_ms();
    if (!ScheduleTimer(loadTimeoutMs)) {
      mLoadTimedOut = true;
      CollectOrScheduleRetry();
    }
  }

  // Reacts to the document loading by starting the collection process.
  void OnDocumentLoaded() {
    if (mCompleted) {
      return;
    }

    CancelTimer();
    CollectOrScheduleRetry();
  }

  // Resolves the request without language metadata.
  void Cancel() {
    CancelTimer();
    Resolve(Nothing());
  }

  // Returns whether the request has completed.
  bool IsCompleted() const { return mCompleted; }

 private:
  ~DocumentLanguageMetadataRequest() { CancelTimer(); }

  // Schedules another attempt to collect language metadata.
  bool ScheduleRetry() {
    mRetryCount++;
    uint32_t retryDelayMs =
        StaticPrefs::dom_document_language_metadata_retry_delay_base_ms() *
        mRetryCount;
    return ScheduleTimer(retryDelayMs);
  }

  // Schedules language metadata collection after the requested delay.
  bool ScheduleTimer(uint32_t aDelayMs) {
    CancelTimer();

    WeakPtr<WindowGlobalChild> weakWindowGlobalChild = mWindowGlobalChild;
    RefPtr<DocumentLanguageMetadataRequest> request = this;
    nsresult rv = NS_NewTimerWithCallback(
        getter_AddRefs(mTimer),
        [weakWindowGlobalChild, request](nsITimer*) {
          RefPtr<WindowGlobalChild> windowGlobalChild =
              weakWindowGlobalChild.get();

          if (!windowGlobalChild) {
            return;
          }

          if (!request->mCompleted) {
            if (request->mRetryCount == 0) {
              request->mLoadTimedOut = true;
            }
            request->CancelTimer();
            request->CollectOrScheduleRetry();
          }
          windowGlobalChild->RemoveCompletedDocumentLanguageMetadataRequests();
        },
        aDelayMs, nsITimer::TYPE_ONE_SHOT,
        "WindowGlobalChild::DocumentLanguageMetadataRequest"_ns);
    return NS_SUCCEEDED(rv);
  }

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

  // Returns whether another collection attempt should be made.
  bool ShouldRetry(const DocumentLanguageMetadata& aMetadata) const {
    if (mLoadTimedOut) {
      // The page did not load in time. We will make only one attempt
      // to extract whatever text is available.
      return false;
    }

    uint32_t maxRetries =
        StaticPrefs::dom_document_language_metadata_max_retries();
    return aMetadata.mTextSample.Length() < mTextSampleMinCodeUnits &&
           mRetryCount < maxRetries;
  }

  // Collects language metadata. If the text sample is too short, the request
  // may retry before resolving with the most recently collected metadata.
  void CollectOrScheduleRetry() {
    if (mCompleted) {
      return;
    }

    RefPtr<WindowGlobalChild> windowGlobalChild = mWindowGlobalChild.get();
    if (!windowGlobalChild ||
        !windowGlobalChild->CanCollectDocumentLanguageMetadata()) {
      Cancel();
      return;
    }

    Maybe<DocumentLanguageMetadata> metadata =
        windowGlobalChild->GetDocumentLanguageMetadata(
            mTextSampleTargetCodeUnits);
    if (metadata.isNothing()) {
      Cancel();
      return;
    }

    if (ShouldRetry(*metadata)) {
      if (ScheduleRetry()) {
        return;
      }
    }

    Resolve(std::move(metadata));
  }

  // Resolves the request with the provided result.
  void Resolve(Maybe<DocumentLanguageMetadata>&& aMetadata) {
    if (mCompleted) {
      return;
    }

    mCompleted = true;
    mWindowGlobalChild = nullptr;
    CancelTimer();

    auto resolver = std::move(mResolver);
    resolver(std::move(aMetadata));
  }

  WeakPtr<WindowGlobalChild> mWindowGlobalChild;
  RequestDocumentLanguageMetadataResolver mResolver;
  nsCOMPtr<nsITimer> mTimer;
  uint32_t mTextSampleMinCodeUnits;
  uint32_t mTextSampleTargetCodeUnits;
  uint32_t mRetryCount = 0;
  bool mCompleted = false;
  bool mLoadTimedOut = false;
};

WindowGlobalChild::WindowGlobalChild(dom::WindowContext* aWindowContext,
                                     nsIPrincipal* aPrincipal,
                                     nsIURI* aDocumentURI)
    : mWindowContext(aWindowContext),
      mDocumentPrincipal(aPrincipal),
      mDocumentURI(aDocumentURI) {
  MOZ_DIAGNOSTIC_ASSERT(mWindowContext);
  MOZ_DIAGNOSTIC_ASSERT(mDocumentPrincipal);
  MOZ_DIAGNOSTIC_ASSERT(mDocumentPrincipal->GetIsLocalIpAddress() ==
                        mWindowContext->IsLocalIP());

  if (!mDocumentURI) {
    NS_NewURI(getter_AddRefs(mDocumentURI), "about:blank");
  }

  // Registers a DOM Window with the profiler. It re-registers the same Inner
  // Window ID with different URIs because when a Browsing context is first
  // loaded, the first url loaded in it will be about:blank. This call keeps the
  // first non-about:blank registration of window and discards the previous one.
  uint64_t embedderInnerWindowID = 0;
  if (auto* parent = WindowContext()->GetParentWindowContext()) {
    embedderInnerWindowID = parent->InnerWindowId();
  }
  profiler_register_page(
      BrowsingContext()->BrowserId(), InnerWindowId(),
      nsContentUtils::TruncatedURLForDisplay(aDocumentURI, 1024),
      embedderInnerWindowID, BrowsingContext()->UsePrivateBrowsing());
}

already_AddRefed<WindowGlobalChild> WindowGlobalChild::Create(
    nsGlobalWindowInner* aWindow) {
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
  // Opener policy is set when we start to load a document. Here, we ensure we
  // have set the correct Opener policy so that it will be available in the
  // parent process through window global child.
  nsCOMPtr<nsIChannel> chan = aWindow->GetDocument()->GetChannel();
  nsCOMPtr<nsILoadInfo> loadInfo = chan ? chan->LoadInfo() : nullptr;
  nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(chan);
  nsILoadInfo::CrossOriginOpenerPolicy policy;
  if (httpChan &&
      loadInfo->GetExternalContentPolicyType() ==
          ExtContentPolicy::TYPE_DOCUMENT &&
      NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy))) {
    MOZ_DIAGNOSTIC_ASSERT(policy ==
                          aWindow->GetBrowsingContext()->GetOpenerPolicy());
  }
#endif

  WindowGlobalInit init = WindowGlobalActor::WindowInitializer(aWindow);
  RefPtr<WindowGlobalChild> wgc = CreateDisconnected(init);

  // Send the link constructor over PBrowser, or link over PInProcess.
  if (XRE_IsParentProcess()) {
    InProcessChild* ipChild = InProcessChild::Singleton();
    InProcessParent* ipParent = InProcessParent::Singleton();
    if (!ipChild || !ipParent) {
      return nullptr;
    }

    ManagedEndpoint<PWindowGlobalParent> endpoint =
        ipChild->OpenPWindowGlobalEndpoint(wgc);
    ipParent->BindPWindowGlobalEndpoint(std::move(endpoint),
                                        wgc->WindowContext()->Canonical());
  } else {
    RefPtr<BrowserChild> browserChild =
        BrowserChild::GetFrom(static_cast<mozIDOMWindow*>(aWindow));
    MOZ_ASSERT(browserChild);

#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
    dom::BrowsingContext* bc = aWindow->GetBrowsingContext();
#endif

    MOZ_DIAGNOSTIC_ASSERT(bc->AncestorsAreCurrent());
    MOZ_DIAGNOSTIC_ASSERT(bc->IsInProcess());

    MOZ_RELEASE_ASSERT(VerifyPartitionedPrincipalMatchesDocumentPrincipal(
        init.principal(), init.partitionedPrincipal()));

    ManagedEndpoint<PWindowGlobalParent> endpoint =
        browserChild->OpenPWindowGlobalEndpoint(wgc);
    browserChild->SendNewWindowGlobal(std::move(endpoint), init);
  }

  wgc->Init();
  wgc->InitWindowGlobal(aWindow);
  return wgc.forget();
}

already_AddRefed<WindowGlobalChild> WindowGlobalChild::CreateDisconnected(
    const WindowGlobalInit& aInit) {
  RefPtr<dom::BrowsingContext> browsingContext =
      dom::BrowsingContext::Get(aInit.context().mBrowsingContextId);

  RefPtr<dom::WindowContext> windowContext =
      dom::WindowContext::GetById(aInit.context().mInnerWindowId);
  MOZ_RELEASE_ASSERT(!windowContext, "Creating duplicate WindowContext");

  // Create our new WindowContext
  if (XRE_IsParentProcess()) {
    windowContext = WindowGlobalParent::CreateDisconnected(aInit, nullptr);
  } else {
    dom::WindowContext::FieldValues fields = aInit.context().mFields;
    windowContext = new dom::WindowContext(
        browsingContext, aInit.context().mInnerWindowId,
        aInit.context().mOuterWindowId, std::move(fields));
  }

  RefPtr<WindowGlobalChild> windowChild = new WindowGlobalChild(
      windowContext, aInit.principal(), aInit.documentURI());
  windowContext->mIsInProcess = true;
  windowContext->mWindowGlobalChild = windowChild;
  return windowChild.forget();
}

void WindowGlobalChild::Init() {
  MOZ_ASSERT(mWindowContext->mWindowGlobalChild == this);
  mWindowContext->Init();
}

void WindowGlobalChild::InitWindowGlobal(nsGlobalWindowInner* aWindow) {
  mWindowGlobal = aWindow;
}

void WindowGlobalChild::OnNewDocument(Document* aDocument) {
  MOZ_RELEASE_ASSERT(mWindowGlobal);
  MOZ_RELEASE_ASSERT(aDocument);

  MOZ_RELEASE_ASSERT(mDocumentPrincipal->Equals(aDocument->NodePrincipal()),
                     "Cannot change document principal origin");
  MOZ_RELEASE_ASSERT(
      VerifyPartitionedPrincipalMatchesDocumentPrincipal(
          aDocument->NodePrincipal(), aDocument->PartitionedPrincipal()),
      "Invalid partitioned principal");

  CancelDocumentLanguageMetadataRequests();

  mDocumentPrincipal = aDocument->NodePrincipal();

  // Send a series of messages to update document-specific state on
  // WindowGlobalParent, when we change documents on an existing WindowGlobal.
  // This data is also all sent when we construct a WindowGlobal, so anything
  // added here should also be added to WindowGlobalActor::WindowInitializer.

  // FIXME: Perhaps these should be combined into a smaller number of messages?
  SendSetIsInitialDocument(aDocument->IsInitialDocument());
  SetDocumentURI(aDocument->GetDocumentURI());
  SendUpdateDocumentPrincipal(WrapNotNull(aDocument->NodePrincipal()),
                              WrapNotNull(aDocument->PartitionedPrincipal()));
  SendUpdatePrincipalPartitioning(!aDocument->UseRegularPrincipal());

  RefPtr<ParentProcessChannelHandle> documentChannelHandle;
  if (nsIChannel* chan = aDocument->GetChannel()) {
    (void)chan->GetParentProcessChannelHandle(
        getter_AddRefs(documentChannelHandle));
  }
  RefPtr<ParentProcessChannelHandle> failedChannelHandle;
  if (nsIChannel* chan = aDocument->GetFailedChannel()) {
    (void)chan->GetParentProcessChannelHandle(
        getter_AddRefs(failedChannelHandle));
  }
  SendUpdateChannels(documentChannelHandle, failedChannelHandle);

  SendUpdateDocumentCspSettings(aDocument->GetBlockAllMixedContent(false),
                                aDocument->GetUpgradeInsecureRequests(false));
  SendUpdateSandboxFlags(aDocument->GetSandboxFlags());

  net::CookieJarSettingsArgs csArgs;
  net::CookieJarSettings::Cast(aDocument->CookieJarSettings())
      ->Serialize(csArgs);
  if (!SendUpdateCookieJarSettings(csArgs)) {
    NS_WARNING(
        "Failed to update document's cookie jar settings on the "
        "WindowGlobalParent");
  }

  SendUpdateHttpsOnlyStatus(aDocument->HttpsOnlyStatus());

  // Update window context fields for the newly loaded Document.
  WindowContext::Transaction txn;
  txn.SetCookieBehavior(
      Some(aDocument->CookieJarSettings()->GetCookieBehavior()));
  txn.SetIsOnContentBlockingAllowList(
      aDocument->CookieJarSettings()->GetIsOnContentBlockingAllowList());
  txn.SetIsThirdPartyWindow(aDocument->HasThirdPartyChannel());
  txn.SetIsThirdPartyTrackingResourceWindow(
      nsContentUtils::IsThirdPartyTrackingResourceWindow(mWindowGlobal));
  txn.SetIsSecureContext(mWindowGlobal->IsSecureContext());
  txn.SetIsFramebustingAllowed(
      mWindowGlobal->GetBrowsingContext()->ComputeIsFramebustingAllowed());
  if (auto policy = aDocument->GetEmbedderPolicy()) {
    txn.SetEmbedderPolicy(*policy);
  }
  txn.SetShouldResistFingerprinting(aDocument->ShouldResistFingerprinting(
      RFPTarget::IsAlwaysEnabledForPrecompute));
  txn.SetOverriddenFingerprintingSettings(
      aDocument->GetOverriddenFingerprintingSettings());

  if (nsCOMPtr<nsIChannel> channel = aDocument->GetChannel()) {
    nsCOMPtr<nsILoadInfo> loadInfo(channel->LoadInfo());
    txn.SetIsOriginalFrameSource(loadInfo->GetOriginalFrameSrcLoad());

    nsILoadInfo::StoragePermissionState storageAccess =
        loadInfo->GetStoragePermission();
    txn.SetUsingStorageAccess(
        storageAccess == nsILoadInfo::HasStoragePermission ||
        storageAccess == nsILoadInfo::StoragePermissionAllowListed);
  } else {
    txn.SetIsOriginalFrameSource(false);
  }

  // Init Mixed Content Fields
  nsCOMPtr<nsIURI> innerDocURI =
      NS_GetInnermostURI(aDocument->GetDocumentURI());
  if (innerDocURI) {
    txn.SetIsSecure(innerDocURI->SchemeIs("https"));
  }

  MOZ_DIAGNOSTIC_ASSERT(mDocumentPrincipal->GetIsLocalIpAddress() ==
                        mWindowContext->IsLocalIP());

  MOZ_ALWAYS_SUCCEEDS(txn.Commit(mWindowContext));
}

void WindowGlobalChild::OnDocumentLoaded() {
  for (const RefPtr<DocumentLanguageMetadataRequest>& request :
       mDocumentLanguageMetadataRequests) {
    request->OnDocumentLoaded();
  }
  RemoveCompletedDocumentLanguageMetadataRequests();
}

void WindowGlobalChild::OnDocumentUnloaded() {
  CancelDocumentLanguageMetadataRequests();
}

bool WindowGlobalChild::CanCollectDocumentLanguageMetadata() {
  if (!mWindowGlobal || IsClosed() || !mWindowGlobal->IsCurrentInnerWindow()) {
    return false;
  }

  Document* document = mWindowGlobal->GetExtantDoc();
  if (!document || !document->IsTopLevelContentDocument() ||
      document->IsInitialDocument() || !document->IsCurrentActiveDocument() ||
      document->GetWindowGlobalChild() != this) {
    return false;
  }

  nsIURI* uri = document->GetDocumentURI();
  if (!uri) {
    return false;
  }

  return uri->SchemeIs("https") || uri->SchemeIs("http") ||
         uri->SchemeIs("file") || uri->SchemeIs("moz-extension");
}

Maybe<DocumentLanguageMetadata> WindowGlobalChild::GetDocumentLanguageMetadata(
    uint32_t aTextSampleTargetCodeUnits) {
  if (!CanCollectDocumentLanguageMetadata()) {
    return Nothing();
  }

  Document* document = mWindowGlobal->GetExtantDoc();
  DocumentLanguageMetadata metadata;

  if (Element* root = document->GetRootElement()) {
    if (nsAtom* langAtom = root->GetLang()) {
      langAtom->ToString(metadata.mHtmlLangAttribute);
    }
  }

  AUTO_PROFILER_MARKER_INNERWINDOWID("DocumentLanguageMetadata", DOM,
                                     InnerWindowId());

  nsCOMPtr<nsIDocumentEncoder> encoder = do_createDocumentEncoder("text/plain");
  uint32_t flags = nsIDocumentEncoder::OutputBodyOnly |
                   nsIDocumentEncoder::SkipInvisibleContent |
                   nsIDocumentEncoder::AllowCrossShadowBoundary |
                   nsIDocumentEncoder::OutputForPlainTextClipboardCopy |
                   nsIDocumentEncoder::OutputDisallowLineBreaking |
                   nsIDocumentEncoder::OutputDropInvisibleBreak |
                   nsIDocumentEncoder::OutputLFLineBreak;

  nsresult rv = encoder->Init(document, u"text/plain"_ns, flags);
  if (NS_FAILED(rv)) {
    return Some(std::move(metadata));
  }

  nsAutoString textSample;
  rv = encoder->EncodeToStringWithMaxLength(aTextSampleTargetCodeUnits,
                                            textSample);
  if (NS_SUCCEEDED(rv)) {
    metadata.mTextSample = textSample;
  }

  return Some(std::move(metadata));
}

void WindowGlobalChild::RemoveCompletedDocumentLanguageMetadataRequests() {
  mDocumentLanguageMetadataRequests.RemoveElementsBy(
      [](const RefPtr<DocumentLanguageMetadataRequest>& aRequest) {
        return aRequest->IsCompleted();
      });
}

void WindowGlobalChild::CancelDocumentLanguageMetadataRequests() {
  for (const RefPtr<DocumentLanguageMetadataRequest>& request :
       mDocumentLanguageMetadataRequests) {
    request->Cancel();
  }
  mDocumentLanguageMetadataRequests.Clear();
}

/* static */
already_AddRefed<WindowGlobalChild> WindowGlobalChild::GetByInnerWindowId(
    uint64_t aInnerWindowId) {
  if (RefPtr<dom::WindowContext> context =
          dom::WindowContext::GetById(aInnerWindowId)) {
    return do_AddRef(context->GetWindowGlobalChild());
  }
  return nullptr;
}

dom::BrowsingContext* WindowGlobalChild::BrowsingContext() {
  return mWindowContext->GetBrowsingContext();
}

Nullable<WindowProxyHolder> WindowGlobalChild::GetContentWindow() {
  if (IsCurrentGlobal()) {
    return WindowProxyHolder(BrowsingContext());
  }
  return nullptr;
}

uint64_t WindowGlobalChild::InnerWindowId() {
  return mWindowContext->InnerWindowId();
}

uint64_t WindowGlobalChild::OuterWindowId() {
  return mWindowContext->OuterWindowId();
}

bool WindowGlobalChild::IsCurrentGlobal() {
  return CanSend() && mWindowGlobal->IsCurrentInnerWindow();
}

already_AddRefed<WindowGlobalParent> WindowGlobalChild::GetParentActor() {
  if (!CanSend()) {
    return nullptr;
  }
  IProtocol* otherSide = InProcessChild::ParentActorFor(this);
  return do_AddRef(static_cast<WindowGlobalParent*>(otherSide));
}

already_AddRefed<BrowserChild> WindowGlobalChild::GetBrowserChild() {
  if (IsInProcess() || !CanSend()) {
    return nullptr;
  }
  return do_AddRef(static_cast<BrowserChild*>(Manager()));
}

uint64_t WindowGlobalChild::ContentParentId() {
  if (XRE_IsParentProcess()) {
    return 0;
  }
  return ContentChild::GetSingleton()->GetID();
}

// A WindowGlobalChild is the root in its process if it has no parent, or its
// embedder is in a different process.
bool WindowGlobalChild::IsProcessRoot() {
  if (!BrowsingContext()->GetParent()) {
    return true;
  }

  return !BrowsingContext()->GetEmbedderElement();
}

// When a "beforeunload" handler is added, it's recorded to be able to know when
// dispatching "beforeunload" is needed.
void WindowGlobalChild::BeforeUnloadAdded() {
  // Don't bother notifying the parent if we don't have an IPC link open.
  if (mBeforeUnloadListeners == 0 && CanSend()) {
    (void)mWindowContext->SetNeedsBeforeUnload(true);
  }

  mBeforeUnloadListeners++;
  MOZ_ASSERT(mBeforeUnloadListeners > 0);
}

// This is the inverse of `BeforeUnloadAdded`, making sure that "beforeunload"
// isn't dispatched if all "beforeunload" handlers have been removed.
void WindowGlobalChild::BeforeUnloadRemoved() {
  mBeforeUnloadListeners--;
  MOZ_ASSERT(mBeforeUnloadListeners >= 0);

  if (mBeforeUnloadListeners == 0) {
    (void)mWindowContext->SetNeedsBeforeUnload(false);
  }
}

// This is very similar to what is done for "beforeunload" and uses the same
// state to keep track, but is only ever used for a top level window. It's used
// to be able to track when a "navigate" event needs to be dispatched to the top
// level window's navigation object, which needs to happen right after a
// "beforeunload" event for that window would be dispatched, regardless of if it
// is.
void WindowGlobalChild::NavigateAdded() {
  if (!BrowsingContext()->IsTop()) {
    return;
  }
  BeforeUnloadAdded();
}

// The inverse of `NavigateAdded`, again only ever used for a top level window.
void WindowGlobalChild::NavigateRemoved() {
  if (!BrowsingContext()->IsTop()) {
    return;
  }
  BeforeUnloadRemoved();
}

void WindowGlobalChild::Destroy() {
  CancelDocumentLanguageMetadataRequests();

  JSActorWillDestroy();

  mWindowContext->Discard();

  // Perform async IPC shutdown unless we're not in-process, and our
  // BrowserChild is in the process of being destroyed, which will destroy
  // us as well.
  RefPtr<BrowserChild> browserChild = GetBrowserChild();
  if (!browserChild || !browserChild->IsDestroyed()) {
    SendDestroy();
  }
}

mozilla::ipc::IPCResult WindowGlobalChild::RecvMakeFrameLocal(
    const MaybeDiscarded<dom::BrowsingContext>& aFrameContext,
    uint64_t aPendingSwitchId) {
  MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess());

  MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
          ("RecvMakeFrameLocal ID=%" PRIx64, aFrameContext.ContextId()));

  if (NS_WARN_IF(aFrameContext.IsNullOrDiscarded())) {
    return IPC_OK();
  }
  dom::BrowsingContext* frameContext = aFrameContext.get();

  RefPtr<Element> embedderElt = frameContext->GetEmbedderElement();
  if (NS_WARN_IF(!embedderElt)) {
    return IPC_OK();
  }

  if (NS_WARN_IF(embedderElt->GetDocumentGlobal() != GetWindowGlobal())) {
    return IPC_OK();
  }

  RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(embedderElt);
  MOZ_DIAGNOSTIC_ASSERT(flo, "Embedder must be a nsFrameLoaderOwner");

  // Trigger a process switch into the current process.
  RemotenessOptions options;
  options.mRemoteType = dom::RemoteType::NotRemote().Stringify();
  options.mPendingSwitchID.Construct(aPendingSwitchId);
  options.mSwitchingInProgressLoad = true;
  flo->ChangeRemoteness(options, IgnoreErrors());
  return IPC_OK();
}

mozilla::ipc::IPCResult WindowGlobalChild::RecvMakeFrameRemote(
    const MaybeDiscarded<dom::BrowsingContext>& aFrameContext,
    ManagedEndpoint<PBrowserBridgeChild>&& aEndpoint, const TabId& aTabId,
    const LayersId& aLayersId, MakeFrameRemoteResolver&& aResolve) {
  MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess());

  MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
          ("RecvMakeFrameRemote ID=%" PRIx64, aFrameContext.ContextId()));

  if (!aLayersId.IsValid()) {
    return IPC_FAIL(this, "Received an invalid LayersId");
  }

  // Resolve the promise when this function exits, as we'll have fully unloaded
  // at that point.
  auto scopeExit = MakeScopeExit([&] { aResolve(true); });

  // Get a BrowsingContext if we're not null or discarded. We don't want to
  // early-return before we connect the BrowserBridgeChild, as otherwise we'll
  // never break the channel in the parent.
  RefPtr<dom::BrowsingContext> frameContext;
  if (!aFrameContext.IsDiscarded()) {
    frameContext = aFrameContext.get();
  }

  // Immediately construct the BrowserBridgeChild so we can destroy it cleanly
  // if the process switch fails.
  RefPtr<BrowserBridgeChild> bridge =
      new BrowserBridgeChild(frameContext, aTabId, aLayersId);
  RefPtr<BrowserChild> manager = GetBrowserChild();
  if (NS_WARN_IF(
          !manager->BindPBrowserBridgeEndpoint(std::move(aEndpoint), bridge))) {
    return IPC_OK();
  }

  // Synchronously delete de actor here rather than using SendBeginDestroy(), as
  // we haven't initialized it yet.
  auto deleteBridge =
      MakeScopeExit([&] { BrowserBridgeChild::Send__delete__(bridge); });

  // Immediately tear down the actor if we don't have a valid FrameContext.
  if (NS_WARN_IF(aFrameContext.IsNullOrDiscarded())) {
    return IPC_OK();
  }

  RefPtr<Element> embedderElt = frameContext->GetEmbedderElement();
  if (NS_WARN_IF(!embedderElt)) {
    return IPC_OK();
  }

  if (NS_WARN_IF(embedderElt->GetDocumentGlobal() != GetWindowGlobal())) {
    return IPC_OK();
  }

  RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(embedderElt);
  MOZ_DIAGNOSTIC_ASSERT(flo, "Embedder must be a nsFrameLoaderOwner");

  // Trgger a process switch into the specified process.
  IgnoredErrorResult rv;
  flo->ChangeRemotenessWithBridge(bridge, rv);
  if (NS_WARN_IF(rv.Failed())) {
    return IPC_OK();
  }

  // Everything succeeded, so don't delete the bridge.
  deleteBridge.release();

  return IPC_OK();
}

class PrintCallbackSnapshot final : public nsITimerCallback, public nsINamed {
 public:
  NS_DECL_ISUPPORTS

  // Returns false if there's nothing to wait for, in which case the caller
  // should record the snapshot synchronously.
  static bool MaybeStart(dom::BrowsingContext* aBc,
                         const Maybe<gfx::IntRect>& aRect, float aScale,
                         nscolor aBackgroundColor,
                         gfx::CrossProcessPaintFlags aFlags,
                         WindowGlobalChild::DrawSnapshotResolver&& aResolve) {
    if (!(aFlags & gfx::CrossProcessPaintFlags::ForPrinting)) {
      return false;
    }
    nsCOMPtr<nsIDocShell> ds = aBc->GetDocShell();
    if (!ds) {
      return false;
    }
    RefPtr<Document> doc = ds->GetDocument();
    if (!doc || !DocumentTreeHasPrintCallbacks(*doc)) {
      return false;
    }
    // Flush upfront so that the frame tree walk below finds the canvases.
    nsContentUtils::FlushLayoutForTree(ds->GetWindow());
    RefPtr<PresShell> presShell = doc->GetPresShell();
    if (!presShell) {
      return false;
    }
    MozPrintCallbackRunner runner;
    runner.CollectCanvases(presShell->GetRootFrame());
    if (!runner.HasCanvases()) {
      return false;
    }

    // This matches PaintFragment::Record.
    RefPtr<gfx::DrawTarget> referenceDt = gfx::Factory::CreateDrawTarget(
        gfxPlatform::GetPlatform()->GetSoftwareBackend(), gfx::IntSize(1, 1),
        gfx::SurfaceFormat::B8G8R8A8);
    if (!referenceDt) {
      return false;
    }

    RefPtr self = new PrintCallbackSnapshot(aBc, std::move(runner), aRect,
                                            aScale, aBackgroundColor, aFlags,
                                            std::move(aResolve));
    self->mRunner.DispatchCallbacks(referenceDt, self);
    if (self->mRunner.AreCallbacksDone()) {
      // No callback could be dispatched, so nothing will notify us.
      self->Finish();
    }
    return true;
  }

  NS_IMETHOD Notify(nsITimer*) override {
    if (mRunner.AreCallbacksDone()) {
      Finish();
    }
    return NS_OK;
  }

  NS_IMETHOD GetName(nsACString& aName) override {
    aName.AssignLiteral("PrintCallbackSnapshot");
    return NS_OK;
  }

 private:
  static bool DocumentTreeHasPrintCallbacks(Document& aDoc) {
    if (aDoc.HasPrintCallbacks()) {
      return true;
    }
    bool found = false;
    aDoc.EnumerateSubDocuments([&found](Document& aSubDoc) {
      found = DocumentTreeHasPrintCallbacks(aSubDoc);
      return found ? CallState::Stop : CallState::Continue;
    });
    return found;
  }

  PrintCallbackSnapshot(dom::BrowsingContext* aBc,
                        MozPrintCallbackRunner&& aRunner,
                        const Maybe<gfx::IntRect>& aRect, float aScale,
                        nscolor aBackgroundColor,
                        gfx::CrossProcessPaintFlags aFlags,
                        WindowGlobalChild::DrawSnapshotResolver&& aResolve)
      : mBrowsingContext(aBc),
        mRunner(std::move(aRunner)),
        mRect(aRect),
        mScale(aScale),
        mBackgroundColor(aBackgroundColor),
        mFlags(aFlags),
        mResolve(std::move(aResolve)) {}

  ~PrintCallbackSnapshot() = default;

  void Finish() {
    if (!mResolve) {
      return;
    }
    gfx::PaintFragment fragment = gfx::PaintFragment::Record(
        mBrowsingContext, mRect, mScale, mBackgroundColor, mFlags);
    mRunner.Reset();
    auto resolve = std::move(mResolve);
    mResolve = nullptr;
    resolve(std::move(fragment));
  }

  RefPtr<dom::BrowsingContext> mBrowsingContext;
  MozPrintCallbackRunner mRunner;
  Maybe<gfx::IntRect> mRect;
  float mScale;
  nscolor mBackgroundColor;
  gfx::CrossProcessPaintFlags mFlags;
  WindowGlobalChild::DrawSnapshotResolver mResolve;
};

NS_IMPL_ISUPPORTS(PrintCallbackSnapshot, nsITimerCallback, nsINamed)

mozilla::ipc::IPCResult WindowGlobalChild::RecvDrawSnapshot(
    const Maybe<IntRect>& aRect, const float& aScale,
    const nscolor& aBackgroundColor, const gfx::CrossProcessPaintFlags& aFlags,
    DrawSnapshotResolver&& aResolve) {
  if (PrintCallbackSnapshot::MaybeStart(BrowsingContext(), aRect, aScale,
                                        aBackgroundColor, aFlags,
                                        std::move(aResolve))) {
    return IPC_OK();
  }
  aResolve(gfx::PaintFragment::Record(BrowsingContext(), aRect, aScale,
                                      aBackgroundColor, aFlags));
  return IPC_OK();
}

mozilla::ipc::IPCResult WindowGlobalChild::RecvRequestDocumentLanguageMetadata(
    uint32_t aTextSampleMinCodeUnits, uint32_t aTextSampleTargetCodeUnits,
    RequestDocumentLanguageMetadataResolver&& aResolver) {
  MOZ_ASSERT(aTextSampleMinCodeUnits <= aTextSampleTargetCodeUnits);

  RefPtr request = MakeRefPtr<DocumentLanguageMetadataRequest>(
      this, aTextSampleMinCodeUnits, aTextSampleTargetCodeUnits,
      std::move(aResolver));
  mDocumentLanguageMetadataRequests.AppendElement(request);
  request->Start();
  RemoveCompletedDocumentLanguageMetadataRequests();

  return IPC_OK();
}

mozilla::ipc::IPCResult
WindowGlobalChild::RecvSaveStorageAccessPermissionGranted() {
  nsCOMPtr<nsPIDOMWindowInner> inner = GetWindowGlobal();
  if (inner) {
    inner->SaveStorageAccessPermissionGranted();
  }

  return IPC_OK();
}

mozilla::ipc::IPCResult WindowGlobalChild::RecvDispatchSecurityPolicyViolation(
    const nsString& aViolationEventJSON, const nsString& aReportGroupName) {
  nsGlobalWindowInner* window = GetWindowGlobal();
  if (!window) {
    return IPC_OK();
  }

  Document* doc = window->GetDocument();
  if (!doc) {
    return IPC_OK();
  }

  ReportingUtils::DeserializeSecurityViolationEventAndReport(
      doc->GetTargetForDOMEvent(), window, aViolationEventJSON,
      aReportGroupName);

  return IPC_OK();
}

mozilla::ipc::IPCResult WindowGlobalChild::RecvAddBlockedFrameNodeByClassifier(
    const MaybeDiscardedBrowsingContext& aNode) {
  if (aNode.IsNullOrDiscarded()) {
    return IPC_OK();
  }

  nsGlobalWindowInner* window = GetWindowGlobal();
  if (!window) {
    return IPC_OK();
  }

  Document* doc = window->GetDocument();
  if (!doc) {
    return IPC_OK();
  }

  MOZ_ASSERT(aNode.get()->GetEmbedderElement()->OwnerDoc() == doc);
  doc->AddBlockedNodeByClassifier(aNode.get()->GetEmbedderElement());
  return IPC_OK();
}

mozilla::ipc::IPCResult WindowGlobalChild::RecvResetScalingZoom() {
  if (Document* doc = mWindowGlobal->GetExtantDoc()) {
    if (PresShell* ps = doc->GetPresShell()) {
      ps->SetResolutionAndScaleTo(1.0,
                                  ResolutionChangeOrigin::MainThreadAdjustment);
    }
  }
  return IPC_OK();
}

mozilla::ipc::IPCResult WindowGlobalChild::RecvRestoreDocShellState(
    const dom::sessionstore::DocShellRestoreState& aState,
    RestoreDocShellStateResolver&& aResolve) {
  if (mWindowGlobal) {
    SessionStoreUtils::RestoreDocShellState(mWindowGlobal->GetDocShell(),
                                            aState);
  }
  aResolve(true);
  return IPC_OK();
}

mozilla::ipc::IPCResult WindowGlobalChild::RecvRestoreTabContent(
    dom::SessionStoreRestoreData* aData, RestoreTabContentResolver&& aResolve) {
  aData->RestoreInto(BrowsingContext());
  aResolve(true);
  return IPC_OK();
}

IPCResult WindowGlobalChild::RecvRawMessage(const JSActorMessageMeta& aMeta,
                                            JSIPCValue&& aData,
                                            StructuredCloneData* aStack) {
  ReceiveRawMessage(aMeta, std::move(aData), aStack);
  return IPC_OK();
}

IPCResult WindowGlobalChild::RecvNotifyAudioSessionStateChanged(
    const AudioSessionState& aState) {
  nsGlobalWindowInner* window = GetWindowGlobal();
  if (!window) {
    return IPC_OK();
  }
  if (RefPtr<dom::AudioSession> session = window->Navigator()->AudioSession()) {
    session->SetState(aState);
  }
  return IPC_OK();
}

IPCResult WindowGlobalChild::RecvNotifyPermissionChange(const nsCString& aType,
                                                        uint32_t aPermission) {
  nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
  NS_ENSURE_TRUE(observerService,
                 IPC_FAIL(this, "Failed to get observer service"));
  nsPIDOMWindowInner* notifyTarget =
      static_cast<nsPIDOMWindowInner*>(this->GetWindowGlobal());
  observerService->NotifyObservers(notifyTarget, "perm-changed-notify-only",
                                   NS_ConvertUTF8toUTF16(aType).get());
  // We only need to handle the revoked permission case here. The permission
  // grant case is handled via the Storage Access API code.
  if (this->GetWindowGlobal() &&
      this->GetWindowGlobal()->UsingStorageAccess() &&
      aPermission != nsIPermissionManager::ALLOW_ACTION) {
    this->GetWindowGlobal()->SaveStorageAccessPermissionRevoked();
  }
  return IPC_OK();
}

IPCResult WindowGlobalChild::RecvProcessCloseRequest(
    const MaybeDiscarded<dom::BrowsingContext>& aFocused) {
  RefPtr<nsFocusManager> focusManager = nsFocusManager::GetFocusManager();
  RefPtr<dom::BrowsingContext> focusedContext =
      focusManager ? focusManager->GetFocusedBrowsingContext() : nullptr;
  if (!focusedContext) {
    return IPC_OK();
  }
  // Only the currently focused context's CloseWatcher should be processed.
  if (RefPtr<Document> doc = focusedContext->GetExtantDocument()) {
    RefPtr<nsPIDOMWindowInner> win = doc->GetInnerWindow();
    if (win && win->IsFullyActive()) {
      RefPtr manager = win->EnsureCloseWatcherManager();
      manager->ProcessCloseRequest();
    }
  }
  return IPC_OK();
}

IPCResult WindowGlobalChild::RecvGetModelContextTools(
    GetModelContextToolsResolver&& aResolver) {
  if (!IsCurrentGlobal()) {
    aResolver(std::make_tuple(NS_ERROR_DOM_INVALID_ACCESS_ERR,
                              nsTArray<IPCModelContextToolDefinition>()));
    return IPC_OK();
  }

  if (!BrowsingContext()->IsTop()) {
    aResolver(std::make_tuple(NS_ERROR_DOM_INVALID_ACCESS_ERR,
                              nsTArray<IPCModelContextToolDefinition>()));
    return IPC_OK();
  }

  nsTArray<IPCModelContextToolDefinition> tools;
  if (nsGlobalWindowInner* win = GetWindowGlobal()) {
    if (Navigator* nav = win->Navigator()) {
      if (ModelContext* mc = nav->ModelContext()) {
        mc->GetIPCToolDefinitions(tools);
      }
    }
  }

  aResolver(std::make_tuple(NS_OK, std::move(tools)));
  return IPC_OK();
}

IPCResult WindowGlobalChild::RecvGetContentMetrics(
    GetContentMetricsResolver&& aResolver) {
  CSSSize size;
  float devicePixelRatio = 1.0f;

  if (IsCurrentGlobal()) {
    if (RefPtr<nsGlobalWindowInner> win = GetWindowGlobal()) {
      if (RefPtr<Document> doc = win->GetExtantDoc()) {
        if (RefPtr<Element> root = doc->GetDocumentElement()) {
          size = CSSPixel::FromAppUnits(root->GetScrollSize());
        }
      }

      IgnoredErrorResult rv;
      double dpr = win->GetDevicePixelRatio(CallerType::System, rv);
      if (!rv.Failed() && dpr > 0.0) {
        devicePixelRatio = float(dpr);
      }
    }
  }
  aResolver(std::make_tuple(size, devicePixelRatio));
  return IPC_OK();
}

IPCResult WindowGlobalChild::RecvInvokeModelContextTool(
    const nsCString& aToolName, NotNull<StructuredCloneData*> aInput,
    InvokeModelContextToolResolver&& aResolver) {
  if (!IsCurrentGlobal()) {
    aResolver(std::make_tuple(
        CopyableErrorResult(NS_ERROR_DOM_INVALID_ACCESS_ERR), nullptr));
    return IPC_OK();
  }

  if (!BrowsingContext()->IsTop()) {
    aResolver(std::make_tuple(
        CopyableErrorResult(NS_ERROR_DOM_INVALID_ACCESS_ERR), nullptr));
    return IPC_OK();
  }

  nsGlobalWindowInner* win = GetWindowGlobal();
  Navigator* nav = win ? win->Navigator() : nullptr;
  RefPtr<ModelContext> mc = nav ? nav->ModelContext() : nullptr;
  if (!mc) {
    aResolver(std::make_tuple(
        CopyableErrorResult(NS_ERROR_DOM_INVALID_ACCESS_ERR), nullptr));
    return IPC_OK();
  }

  AutoJSAPI jsapi;
  if (!jsapi.Init(win)) {
    aResolver(std::make_tuple(CopyableErrorResult(NS_ERROR_FAILURE), nullptr));
    return IPC_OK();
  }
  JSContext* cx = jsapi.cx();

  JS::Rooted<JS::Value> inputVal(cx);
  IgnoredErrorResult deserializeRv;
  aInput->Read(cx, &inputVal, deserializeRv);
  if (deserializeRv.Failed()) {
    aResolver(std::make_tuple(CopyableErrorResult(NS_ERROR_DOM_DATA_CLONE_ERR),
                              nullptr));
    return IPC_OK();
  }

  ErrorResult rv;
  RefPtr<Promise> domPromise = mc->InvokeToolInternal(
      cx, NS_ConvertUTF8toUTF16(aToolName), inputVal, rv);
  if (!domPromise) {
    aResolver(std::make_tuple(CopyableErrorResult(std::move(rv)), nullptr));
    return IPC_OK();
  }

  domPromise->AddCallbacksWithCycleCollectedArgs(
      [aResolver](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult&) {
        RefPtr<StructuredCloneData> cloneData =
            MakeRefPtr<StructuredCloneData>();
        IgnoredErrorResult rv;
        cloneData->Write(aCx, aValue, rv);
        if (rv.Failed()) {
          aResolver(std::make_tuple(
              CopyableErrorResult(NS_ERROR_DOM_DATA_CLONE_ERR), nullptr));
          return;
        }
        aResolver(std::make_tuple(CopyableErrorResult(), std::move(cloneData)));
      },
      [aResolver](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult&) {
        RefPtr<StructuredCloneData> cloneData =
            MakeRefPtr<StructuredCloneData>();
        IgnoredErrorResult rv;
        cloneData->Write(aCx, aValue, rv);
        if (rv.Failed()) {
          aResolver(
              std::make_tuple(CopyableErrorResult(NS_ERROR_FAILURE), nullptr));
          return;
        }
        aResolver(std::make_tuple(CopyableErrorResult(NS_ERROR_FAILURE),
                                  std::move(cloneData)));
      });
  return IPC_OK();
}

void WindowGlobalChild::SetDocumentURI(nsIURI* aDocumentURI) {
  // Registers a DOM Window with the profiler. It re-registers the same Inner
  // Window ID with different URIs because when a Browsing context is first
  // loaded, the first url loaded in it will be about:blank. This call keeps the
  // first non-about:blank registration of window and discards the previous one.
  uint64_t embedderInnerWindowID = 0;
  if (auto* parent = WindowContext()->GetParentWindowContext()) {
    embedderInnerWindowID = parent->InnerWindowId();
  }
  profiler_register_page(
      BrowsingContext()->BrowserId(), InnerWindowId(),
      nsContentUtils::TruncatedURLForDisplay(aDocumentURI, 1024),
      embedderInnerWindowID, BrowsingContext()->UsePrivateBrowsing());

  nsCOMPtr<nsIURI> principalURI = mDocumentPrincipal->GetURI();
  if (mDocumentPrincipal->GetIsNullPrincipal()) {
    if (nsCOMPtr<nsIPrincipal> precursor =
            mDocumentPrincipal->GetPrecursorPrincipal()) {
      principalURI = precursor->GetURI();
    }
  }

  MOZ_DIAGNOSTIC_ASSERT(!nsScriptSecurityManager::IsHttpOrHttpsAndCrossOrigin(
                            principalURI, aDocumentURI),
                        "Setting DocumentURI with a different origin "
                        "than principal URI");

  mDocumentURI = aDocumentURI;
  SendUpdateDocumentURI(WrapNotNull(aDocumentURI));
}

const RemoteType& WindowGlobalChild::GetRemoteType() const {
  if (XRE_IsContentProcess()) {
    return ContentChild::GetSingleton()->GetRemoteType();
  }

  return RemoteType::NotRemote();
}

already_AddRefed<JSWindowActorChild> WindowGlobalChild::GetActor(
    JSContext* aCx, const nsACString& aName, ErrorResult& aRv) {
  return JSActorManager::GetActor(aCx, aName, aRv)
      .downcast<JSWindowActorChild>();
}

already_AddRefed<JSWindowActorChild> WindowGlobalChild::GetExistingActor(
    const nsACString& aName) {
  return JSActorManager::GetExistingActor(aName).downcast<JSWindowActorChild>();
}

already_AddRefed<JSActor> WindowGlobalChild::InitJSActor(
    JS::Handle<JSObject*> aMaybeActor, const nsACString& aName,
    ErrorResult& aRv) {
  RefPtr<JSWindowActorChild> actor;
  if (aMaybeActor.get()) {
    aRv = UNWRAP_OBJECT(JSWindowActorChild, aMaybeActor.get(), actor);
    if (aRv.Failed()) {
      return nullptr;
    }
  } else {
    actor = new JSWindowActorChild();
  }

  MOZ_RELEASE_ASSERT(!actor->GetManager(),
                     "mManager was already initialized once!");
  actor->Init(aName, this);
  return actor.forget();
}

void WindowGlobalChild::ActorDestroy(ActorDestroyReason aWhy) {
  MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(),
             "Destroying WindowGlobalChild can run script");

  CancelDocumentLanguageMetadataRequests();

  // If our WindowContext hasn't been marked as discarded yet, ensure it's
  // marked as discarded at this point.
  mWindowContext->Discard();

  profiler_unregister_page(InnerWindowId());

  // Destroy our JSActors, and reject any pending queries.
  JSActorDidDestroy();
}

bool WindowGlobalChild::IsSameOriginWith(
    const dom::WindowContext* aOther) const {
  if (aOther == WindowContext()) {
    return true;
  }

  MOZ_DIAGNOSTIC_ASSERT(WindowContext()->Group() == aOther->Group());
  if (nsGlobalWindowInner* otherWin = aOther->GetInnerWindow()) {
    return mDocumentPrincipal->Equals(otherWin->GetPrincipal());
  }
  return false;
}

bool WindowGlobalChild::SameOriginWithTop() {
  return IsSameOriginWith(WindowContext()->TopWindowContext());
}

// For historical context, see:
//
// Bug 13871:   Prevent frameset spoofing
// Bug 103638:  Targets with same name in different windows open in wrong
//              window with javascript
// Bug 408052:  Adopt "ancestor" frame navigation policy
// Bug 1570207: Refactor logic to rely on BrowsingContextGroups to enforce
//              origin attribute isolation
// Bug 1810619: Crash at null in nsDocShell::ValidateOrigin
bool WindowGlobalChild::CanNavigate(dom::BrowsingContext* aTarget,
                                    bool aConsiderOpener) {
  return nsContentUtils::CanNavigate(BrowsingContext(), aTarget,
                                     DocumentPrincipal(), aConsiderOpener);
}

// FindWithName follows the rules for choosing a browsing context,
// with the exception of sandboxing for iframes. The implementation
// for arbitrarily choosing between two browsing contexts with the
// same name is as follows:
//
// 1) The start browsing context, i.e. 'this'
// 2) Descendants in insertion order
// 3) The parent
// 4) Siblings and their children, both in insertion order
// 5) After this we iteratively follow the parent chain, repeating 3
//    and 4 until
// 6) If there is no parent, consider all other top level browsing
//    contexts and their children, both in insertion order
//
// See
// https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name
dom::BrowsingContext* WindowGlobalChild::FindBrowsingContextWithName(
    const nsAString& aName, bool aUseEntryGlobalForAccessCheck) {
  RefPtr<WindowGlobalChild> requestingContext = this;
  if (aUseEntryGlobalForAccessCheck) {
    if (nsGlobalWindowInner* caller = nsContentUtils::EntryInnerWindow()) {
      if (caller->GetBrowsingContextGroup() == WindowContext()->Group()) {
        requestingContext = caller->GetWindowGlobalChild();
      } else {
        MOZ_RELEASE_ASSERT(caller->GetPrincipal()->IsSystemPrincipal(),
                           "caller must be either same-group or system");
      }
    }
  }
  MOZ_ASSERT(requestingContext, "must have a requestingContext");

  dom::BrowsingContext* found = nullptr;
  if (aName.IsEmpty()) {
    // You can't find a browsing context with an empty name.
    found = nullptr;
  } else if (aName.LowerCaseEqualsLiteral("_blank")) {
    // Just return null. Caller must handle creating a new window with
    // a blank name.
    found = nullptr;
  } else if (nsContentUtils::IsSpecialName(aName)) {
    found = BrowsingContext()->FindWithSpecialName(aName, *requestingContext);
  } else if (dom::BrowsingContext* child =
                 BrowsingContext()->FindWithNameInSubtree(aName,
                                                          requestingContext)) {
    found = child;
  } else {
    dom::WindowContext* current = WindowContext();

    do {
      Span<RefPtr<dom::BrowsingContext>> siblings;
      dom::WindowContext* parent = current->GetParentWindowContext();

      if (!parent) {
        // We've reached the root of the tree, consider browsing
        // contexts in the same browsing context group.
        siblings = WindowContext()->Group()->Toplevels();
      } else if (dom::BrowsingContext* bc = parent->GetBrowsingContext();
                 bc && bc->NameEquals(aName) &&
                 requestingContext->CanNavigate(bc) && bc->IsTargetable()) {
        found = bc;
        break;
      } else {
        siblings = parent->NonSyntheticChildren();
      }

      for (dom::BrowsingContext* sibling : siblings) {
        if (sibling == current->GetBrowsingContext()) {
          continue;
        }

        if (dom::BrowsingContext* relative =
                sibling->FindWithNameInSubtree(aName, requestingContext)) {
          found = relative;
          // Breaks the outer loop
          parent = nullptr;
          break;
        }
      }

      current = parent;
    } while (current);
  }

  // Helpers should perform access control checks, which means that we
  // only need to assert that we can access found.
  MOZ_DIAGNOSTIC_ASSERT(!found || requestingContext->CanNavigate(found));

  return found;
}

void WindowGlobalChild::UnblockBFCacheFor(BFCacheStatus aStatus) {
  SendUpdateBFCacheStatus(0, aStatus);
}

void WindowGlobalChild::BlockBFCacheFor(BFCacheStatus aStatus) {
  SendUpdateBFCacheStatus(aStatus, 0);
}

WindowGlobalChild::~WindowGlobalChild() = default;

JSObject* WindowGlobalChild::WrapObject(JSContext* aCx,
                                        JS::Handle<JSObject*> aGivenProto) {
  return WindowGlobalChild_Binding::Wrap(aCx, this, aGivenProto);
}

nsISupports* WindowGlobalChild::GetParentObject() {
  return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
}

NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WindowGlobalChild)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WindowGlobalChild)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindowGlobal)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mContainerPermissionsPolicy)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindowContext)
  tmp->UnlinkManager();
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
  NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WindowGlobalChild)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindowGlobal)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContainerPermissionsPolicy)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindowContext)
  if (!tmp->IsInProcess()) {
    CycleCollectionNoteChild(cb, static_cast<BrowserChild*>(tmp->Manager()),
                             "Manager()");
  }
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WindowGlobalChild)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(WindowGlobalChild)
NS_IMPL_CYCLE_COLLECTING_RELEASE(WindowGlobalChild)

}  // namespace mozilla::dom
