/* -*- 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 "mozilla/dom/WindowContext.h"
#include "mozilla/dom/WindowGlobalActorsBinding.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/SyncedContextInlines.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/UserActivationIPCUtils.h"
#include "mozilla/PermissionDelegateIPCUtils.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/ClearOnShutdown.h"
#include "nsGlobalWindowInner.h"
#include "nsIScriptError.h"
#include "nsIWebProgressListener.h"
#include "nsIXULRuntime.h"
#include "nsRefPtrHashtable.h"
#include "nsContentUtils.h"

namespace mozilla {
namespace dom {

// Explicit specialization of the `Transaction` type. Required by the `extern
// template class` declaration in the header.
template class syncedcontext::Transaction<WindowContext>;

static LazyLogModule gWindowContextLog("WindowContext");
static LazyLogModule gWindowContextSyncLog("WindowContextSync");

extern mozilla::LazyLogModule gUserInteractionPRLog;

#define USER_ACTIVATION_LOG(msg, ...) \
  MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__))

using WindowContextByIdMap = nsTHashMap<nsUint64HashKey, WindowContext*>;
static StaticAutoPtr<WindowContextByIdMap> gWindowContexts;

/* static */
LogModule* WindowContext::GetLog() { return gWindowContextLog; }

/* static */
LogModule* WindowContext::GetSyncLog() { return gWindowContextSyncLog; }

/* static */
already_AddRefed<WindowContext> WindowContext::GetById(
    uint64_t aInnerWindowId) {
  if (!gWindowContexts) {
    return nullptr;
  }
  return do_AddRef(gWindowContexts->Get(aInnerWindowId));
}

BrowsingContextGroup* WindowContext::Group() const {
  return mBrowsingContext->Group();
}

WindowGlobalParent* WindowContext::Canonical() {
  MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
  return static_cast<WindowGlobalParent*>(this);
}

bool WindowContext::IsCurrent() const {
  return mBrowsingContext->mCurrentWindowContext == this;
}

bool WindowContext::IsInBFCache() {
  if (mozilla::SessionHistoryInParent()) {
    return mBrowsingContext->IsInBFCache();
  }
  return TopWindowContext()->GetWindowStateSaved();
}

nsGlobalWindowInner* WindowContext::GetInnerWindow() const {
  return mWindowGlobalChild ? mWindowGlobalChild->GetWindowGlobal() : nullptr;
}

Document* WindowContext::GetDocument() const {
  nsGlobalWindowInner* innerWindow = GetInnerWindow();
  return innerWindow ? innerWindow->GetDocument() : nullptr;
}

Document* WindowContext::GetExtantDoc() const {
  nsGlobalWindowInner* innerWindow = GetInnerWindow();
  return innerWindow ? innerWindow->GetExtantDoc() : nullptr;
}

WindowGlobalChild* WindowContext::GetWindowGlobalChild() const {
  return mWindowGlobalChild;
}

WindowContext* WindowContext::GetParentWindowContext() {
  return mBrowsingContext->GetParentWindowContext();
}

WindowContext* WindowContext::TopWindowContext() {
  WindowContext* current = this;
  while (current->GetParentWindowContext()) {
    current = current->GetParentWindowContext();
  }
  return current;
}

bool WindowContext::IsTop() const { return mBrowsingContext->IsTop(); }

bool WindowContext::SameOriginWithTop() const {
  return mBrowsingContext->SameOriginWithTop();
}

nsIGlobalObject* WindowContext::GetParentObject() const {
  return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
}

void WindowContext::AppendChildBrowsingContext(
    BrowsingContext* aBrowsingContext) {
  MOZ_DIAGNOSTIC_ASSERT(Group() == aBrowsingContext->Group(),
                        "Mismatched groups?");
  MOZ_DIAGNOSTIC_ASSERT(!mChildren.Contains(aBrowsingContext));

  mChildren.AppendElement(aBrowsingContext);
  if (!nsContentUtils::ShouldHideObjectOrEmbedImageDocument() ||
      !aBrowsingContext->IsEmbedderTypeObjectOrEmbed()) {
    mNonSyntheticChildren.AppendElement(aBrowsingContext);
  }

  // If we're the current WindowContext in our BrowsingContext, make sure to
  // clear any cached `children` value.
  if (IsCurrent()) {
    BrowsingContext_Binding::ClearCachedChildrenValue(mBrowsingContext);
  }
}

void WindowContext::RemoveChildBrowsingContext(
    BrowsingContext* aBrowsingContext) {
  MOZ_DIAGNOSTIC_ASSERT(Group() == aBrowsingContext->Group(),
                        "Mismatched groups?");

  mChildren.RemoveElement(aBrowsingContext);
  mNonSyntheticChildren.RemoveElement(aBrowsingContext);

  // If we're the current WindowContext in our BrowsingContext, make sure to
  // clear any cached `children` value.
  if (IsCurrent()) {
    BrowsingContext_Binding::ClearCachedChildrenValue(mBrowsingContext);
  }
}

void WindowContext::UpdateChildSynthetic(BrowsingContext* aBrowsingContext,
                                         bool aIsSynthetic) {
  MOZ_ASSERT(nsContentUtils::ShouldHideObjectOrEmbedImageDocument());
  if (aIsSynthetic) {
    mNonSyntheticChildren.RemoveElement(aBrowsingContext);
  } else {
    // The same BrowsingContext will be reused for error pages, so it can be in
    // the list already.
    if (!mNonSyntheticChildren.Contains(aBrowsingContext)) {
      mNonSyntheticChildren.AppendElement(aBrowsingContext);
    }
  }
}

void WindowContext::SendCommitTransaction(ContentParent* aParent,
                                          const BaseTransaction& aTxn,
                                          uint64_t aEpoch) {
  Unused << aParent->SendCommitWindowContextTransaction(this, aTxn, aEpoch);
}

void WindowContext::SendCommitTransaction(ContentChild* aChild,
                                          const BaseTransaction& aTxn,
                                          uint64_t aEpoch) {
  aChild->SendCommitWindowContextTransaction(this, aTxn, aEpoch);
}

bool WindowContext::CheckOnlyOwningProcessCanSet(ContentParent* aSource) {
  if (IsInProcess()) {
    return true;
  }

  if (XRE_IsParentProcess() && aSource) {
    return Canonical()->GetContentParent() == aSource;
  }

  return false;
}

bool WindowContext::CanSet(FieldIndex<IDX_IsSecure>, const bool& aIsSecure,
                           ContentParent* aSource) {
  return CheckOnlyOwningProcessCanSet(aSource);
}

bool WindowContext::CanSet(FieldIndex<IDX_AllowMixedContent>,
                           const bool& aAllowMixedContent,
                           ContentParent* aSource) {
  return CheckOnlyOwningProcessCanSet(aSource);
}

bool WindowContext::CanSet(FieldIndex<IDX_HasBeforeUnload>,
                           const bool& aHasBeforeUnload,
                           ContentParent* aSource) {
  return CheckOnlyOwningProcessCanSet(aSource);
}

bool WindowContext::CanSet(FieldIndex<IDX_CookieBehavior>,
                           const Maybe<uint32_t>& aValue,
                           ContentParent* aSource) {
  return CheckOnlyOwningProcessCanSet(aSource);
}

bool WindowContext::CanSet(FieldIndex<IDX_IsOnContentBlockingAllowList>,
                           const bool& aValue, ContentParent* aSource) {
  return CheckOnlyOwningProcessCanSet(aSource);
}

bool WindowContext::CanSet(FieldIndex<IDX_IsThirdPartyWindow>,
                           const bool& IsThirdPartyWindow,
                           ContentParent* aSource) {
  return CheckOnlyOwningProcessCanSet(aSource);
}

bool WindowContext::CanSet(FieldIndex<IDX_IsThirdPartyTrackingResourceWindow>,
                           const bool& aIsThirdPartyTrackingResourceWindow,
                           ContentParent* aSource) {
  return CheckOnlyOwningProcessCanSet(aSource);
}

bool WindowContext::CanSet(FieldIndex<IDX_ShouldResistFingerprinting>,
                           const bool& aShouldResistFingerprinting,
                           ContentParent* aSource) {
  return CheckOnlyOwningProcessCanSet(aSource);
}

bool WindowContext::CanSet(FieldIndex<IDX_IsSecureContext>,
                           const bool& aIsSecureContext,
                           ContentParent* aSource) {
  return CheckOnlyOwningProcessCanSet(aSource);
}

bool WindowContext::CanSet(FieldIndex<IDX_IsOriginalFrameSource>,
                           const bool& aIsOriginalFrameSource,
                           ContentParent* aSource) {
  return CheckOnlyOwningProcessCanSet(aSource);
}

bool WindowContext::CanSet(FieldIndex<IDX_DocTreeHadMedia>, const bool& aValue,
                           ContentParent* aSource) {
  return IsTop();
}

bool WindowContext::CanSet(FieldIndex<IDX_AutoplayPermission>,
                           const uint32_t& aValue, ContentParent* aSource) {
  return CheckOnlyOwningProcessCanSet(aSource);
}

bool WindowContext::CanSet(FieldIndex<IDX_ShortcutsPermission>,
                           const uint32_t& aValue, ContentParent* aSource) {
  return IsTop() && CheckOnlyOwningProcessCanSet(aSource);
}

bool WindowContext::CanSet(FieldIndex<IDX_ActiveMediaSessionContextId>,
                           const Maybe<uint64_t>& aValue,
                           ContentParent* aSource) {
  return IsTop();
}

bool WindowContext::CanSet(FieldIndex<IDX_PopupPermission>, const uint32_t&,
                           ContentParent* aSource) {
  return CheckOnlyOwningProcessCanSet(aSource);
}

bool WindowContext::CanSet(
    FieldIndex<IDX_DelegatedPermissions>,
    const PermissionDelegateHandler::DelegatedPermissionList& aValue,
    ContentParent* aSource) {
  return CheckOnlyOwningProcessCanSet(aSource);
}

bool WindowContext::CanSet(
    FieldIndex<IDX_DelegatedExactHostMatchPermissions>,
    const PermissionDelegateHandler::DelegatedPermissionList& aValue,
    ContentParent* aSource) {
  return CheckOnlyOwningProcessCanSet(aSource);
}

bool WindowContext::CanSet(FieldIndex<IDX_IsLocalIP>, const bool& aValue,
                           ContentParent* aSource) {
  return CheckOnlyOwningProcessCanSet(aSource);
}

bool WindowContext::CanSet(FieldIndex<IDX_AllowJavascript>, bool aValue,
                           ContentParent* aSource) {
  return (XRE_IsParentProcess() && !aSource) ||
         CheckOnlyOwningProcessCanSet(aSource);
}

void WindowContext::DidSet(FieldIndex<IDX_AllowJavascript>, bool aOldValue) {
  RecomputeCanExecuteScripts();
}

bool WindowContext::CanSet(FieldIndex<IDX_HasActivePeerConnections>, bool,
                           ContentParent*) {
  return XRE_IsParentProcess() && IsTop();
}

void WindowContext::RecomputeCanExecuteScripts(bool aApplyChanges) {
  const bool old = mCanExecuteScripts;
  if (!AllowJavascript()) {
    // Scripting has been explicitly disabled on our WindowContext.
    mCanExecuteScripts = false;
  } else {
    // Otherwise, inherit.
    mCanExecuteScripts = mBrowsingContext->CanExecuteScripts();
  }

  if (aApplyChanges && old != mCanExecuteScripts) {
    // Inform our active DOM window.
    if (nsGlobalWindowInner* window = GetInnerWindow()) {
      // Only update scriptability if the window is current. Windows will have
      // scriptability disabled when entering the bfcache and updated when
      // coming out.
      if (window->IsCurrentInnerWindow()) {
        auto& scriptability =
            xpc::Scriptability::Get(window->GetGlobalJSObject());
        scriptability.SetWindowAllowsScript(mCanExecuteScripts);
      }
    }

    for (const RefPtr<BrowsingContext>& child : Children()) {
      child->RecomputeCanExecuteScripts();
    }
  }
}

void WindowContext::DidSet(FieldIndex<IDX_SHEntryHasUserInteraction>,
                           bool aOldValue) {
  MOZ_ASSERT(
      TopWindowContext() == this,
      "SHEntryHasUserInteraction can only be set on the top window context");
  // This field is set when the child notifies us of new user interaction, so we
  // also set the currently active shentry in the parent as having interaction.
  if (XRE_IsParentProcess() && mBrowsingContext) {
    SessionHistoryEntry* activeEntry =
        mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry();
    if (activeEntry && GetSHEntryHasUserInteraction()) {
      activeEntry->SetHasUserInteraction(true);
    }
  }
}

void WindowContext::DidSet(FieldIndex<IDX_UserActivationState>) {
  MOZ_ASSERT_IF(!IsInProcess(), mUserGestureStart.IsNull());
  USER_ACTIVATION_LOG("Set user gesture activation %" PRIu8
                      " for %s browsing context 0x%08" PRIx64,
                      static_cast<uint8_t>(GetUserActivationState()),
                      XRE_IsParentProcess() ? "Parent" : "Child", Id());
  if (IsInProcess()) {
    USER_ACTIVATION_LOG(
        "Set user gesture start time for %s browsing context 0x%08" PRIx64,
        XRE_IsParentProcess() ? "Parent" : "Child", Id());
    if (GetUserActivationState() == UserActivation::State::FullActivated) {
      mUserGestureStart = TimeStamp::Now();
    } else if (GetUserActivationState() == UserActivation::State::None) {
      mUserGestureStart = TimeStamp();
    }
  }
}

void WindowContext::DidSet(FieldIndex<IDX_HasReportedShadowDOMUsage>,
                           bool aOldValue) {
  if (!aOldValue && GetHasReportedShadowDOMUsage() && IsInProcess()) {
    MOZ_ASSERT(TopWindowContext() == this);
    if (mBrowsingContext) {
      Document* topLevelDoc = mBrowsingContext->GetDocument();
      if (topLevelDoc) {
        nsAutoString uri;
        Unused << topLevelDoc->GetDocumentURI(uri);
        if (!uri.IsEmpty()) {
          nsAutoString msg = u"Shadow DOM used in ["_ns + uri +
                             u"] or in some of its subdocuments."_ns;
          nsContentUtils::ReportToConsoleNonLocalized(
              msg, nsIScriptError::infoFlag, "DOM"_ns, topLevelDoc);
        }
      }
    }
  }
}

bool WindowContext::CanSet(FieldIndex<IDX_WindowStateSaved>, bool aValue,
                           ContentParent* aSource) {
  return !mozilla::SessionHistoryInParent() && IsTop() &&
         CheckOnlyOwningProcessCanSet(aSource);
}

void WindowContext::CreateFromIPC(IPCInitializer&& aInit) {
  MOZ_RELEASE_ASSERT(XRE_IsContentProcess(),
                     "Should be a WindowGlobalParent in the parent");

  RefPtr<BrowsingContext> bc = BrowsingContext::Get(aInit.mBrowsingContextId);
  MOZ_RELEASE_ASSERT(bc);

  if (bc->IsDiscarded()) {
    // If we have already closed our browsing context, the
    // WindowGlobalChild actor is bound to be destroyed soon and it's
    // safe to ignore creating the WindowContext.
    return;
  }

  RefPtr<WindowContext> context = new WindowContext(
      bc, aInit.mInnerWindowId, aInit.mOuterWindowId, std::move(aInit.mFields));
  context->Init();
}

void WindowContext::Init() {
  MOZ_LOG(GetLog(), LogLevel::Debug,
          ("Registering 0x%" PRIx64 " (bc=0x%" PRIx64 ")", mInnerWindowId,
           mBrowsingContext->Id()));

  // Register the WindowContext in the `WindowContextByIdMap`.
  if (!gWindowContexts) {
    gWindowContexts = new WindowContextByIdMap();
    ClearOnShutdown(&gWindowContexts);
  }
  auto& entry = gWindowContexts->LookupOrInsert(mInnerWindowId);
  MOZ_RELEASE_ASSERT(!entry, "Duplicate WindowContext for ID!");
  entry = this;

  // Register this to the browsing context.
  mBrowsingContext->RegisterWindowContext(this);
  Group()->Register(this);
}

void WindowContext::Discard() {
  MOZ_LOG(GetLog(), LogLevel::Debug,
          ("Discarding 0x%" PRIx64 " (bc=0x%" PRIx64 ")", mInnerWindowId,
           mBrowsingContext->Id()));
  if (mIsDiscarded) {
    return;
  }

  mIsDiscarded = true;
  if (gWindowContexts) {
    gWindowContexts->Remove(InnerWindowId());
  }
  mBrowsingContext->UnregisterWindowContext(this);
  Group()->Unregister(this);
}

void WindowContext::AddSecurityState(uint32_t aStateFlags) {
  MOZ_ASSERT(TopWindowContext() == this);
  MOZ_ASSERT((aStateFlags &
              (nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT |
               nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT |
               nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT |
               nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT |
               nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADED |
               nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADE_FAILED |
               nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADED_FIRST)) ==
                 aStateFlags,
             "Invalid flags specified!");

  if (XRE_IsParentProcess()) {
    Canonical()->AddSecurityState(aStateFlags);
  } else {
    ContentChild* child = ContentChild::GetSingleton();
    child->SendAddSecurityState(this, aStateFlags);
  }
}

void WindowContext::NotifyUserGestureActivation() {
  Unused << SetUserActivationState(UserActivation::State::FullActivated);
}

void WindowContext::NotifyResetUserGestureActivation() {
  Unused << SetUserActivationState(UserActivation::State::None);
}

bool WindowContext::HasBeenUserGestureActivated() {
  return GetUserActivationState() != UserActivation::State::None;
}

const TimeStamp& WindowContext::GetUserGestureStart() const {
  MOZ_ASSERT(IsInProcess());
  return mUserGestureStart;
}

bool WindowContext::HasValidTransientUserGestureActivation() {
  MOZ_ASSERT(IsInProcess());

  if (GetUserActivationState() != UserActivation::State::FullActivated) {
    // mUserGestureStart should be null if the document hasn't ever been
    // activated by user gesture
    MOZ_ASSERT_IF(GetUserActivationState() == UserActivation::State::None,
                  mUserGestureStart.IsNull());
    return false;
  }

  MOZ_ASSERT(!mUserGestureStart.IsNull(),
             "mUserGestureStart shouldn't be null if the document has ever "
             "been activated by user gesture");
  TimeDuration timeout = TimeDuration::FromMilliseconds(
      StaticPrefs::dom_user_activation_transient_timeout());

  return timeout <= TimeDuration() ||
         (TimeStamp::Now() - mUserGestureStart) <= timeout;
}

bool WindowContext::ConsumeTransientUserGestureActivation() {
  MOZ_ASSERT(IsInProcess());
  MOZ_ASSERT(IsCurrent());

  if (!HasValidTransientUserGestureActivation()) {
    return false;
  }

  BrowsingContext* top = mBrowsingContext->Top();
  top->PreOrderWalk([&](BrowsingContext* aBrowsingContext) {
    WindowContext* windowContext = aBrowsingContext->GetCurrentWindowContext();
    if (windowContext && windowContext->GetUserActivationState() ==
                             UserActivation::State::FullActivated) {
      Unused << windowContext->SetUserActivationState(
          UserActivation::State::HasBeenActivated);
    }
  });

  return true;
}

bool WindowContext::CanShowPopup() {
  uint32_t permit = GetPopupPermission();
  if (permit == nsIPermissionManager::ALLOW_ACTION) {
    return true;
  }
  if (permit == nsIPermissionManager::DENY_ACTION) {
    return false;
  }

  return !StaticPrefs::dom_disable_open_during_load();
}

void WindowContext::TransientSetHasActivePeerConnections() {
  if (!IsTop()) {
    return;
  }

  mFields.SetWithoutSyncing<IDX_HasActivePeerConnections>(true);
}

WindowContext::IPCInitializer WindowContext::GetIPCInitializer() {
  IPCInitializer init;
  init.mInnerWindowId = mInnerWindowId;
  init.mOuterWindowId = mOuterWindowId;
  init.mBrowsingContextId = mBrowsingContext->Id();
  init.mFields = mFields.RawValues();
  return init;
}

WindowContext::WindowContext(BrowsingContext* aBrowsingContext,
                             uint64_t aInnerWindowId, uint64_t aOuterWindowId,
                             FieldValues&& aInit)
    : mFields(std::move(aInit)),
      mInnerWindowId(aInnerWindowId),
      mOuterWindowId(aOuterWindowId),
      mBrowsingContext(aBrowsingContext) {
  MOZ_ASSERT(mBrowsingContext);
  MOZ_ASSERT(mInnerWindowId);
  MOZ_ASSERT(mOuterWindowId);
  RecomputeCanExecuteScripts(/* aApplyChanges */ false);
}

WindowContext::~WindowContext() {
  if (gWindowContexts) {
    gWindowContexts->Remove(InnerWindowId());
  }
}

JSObject* WindowContext::WrapObject(JSContext* cx,
                                    JS::Handle<JSObject*> aGivenProto) {
  return WindowContext_Binding::Wrap(cx, this, aGivenProto);
}

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WindowContext)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(WindowContext)
NS_IMPL_CYCLE_COLLECTING_RELEASE(WindowContext)

NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WindowContext)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WindowContext)
  if (gWindowContexts) {
    gWindowContexts->Remove(tmp->InnerWindowId());
  }

  NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContext)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildren)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mNonSyntheticChildren)
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WindowContext)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildren)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNonSyntheticChildren)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

}  // namespace dom

namespace ipc {

void IPDLParamTraits<dom::MaybeDiscarded<dom::WindowContext>>::Write(
    IPC::MessageWriter* aWriter, IProtocol* aActor,
    const dom::MaybeDiscarded<dom::WindowContext>& aParam) {
  uint64_t id = aParam.ContextId();
  WriteIPDLParam(aWriter, aActor, id);
}

bool IPDLParamTraits<dom::MaybeDiscarded<dom::WindowContext>>::Read(
    IPC::MessageReader* aReader, IProtocol* aActor,
    dom::MaybeDiscarded<dom::WindowContext>* aResult) {
  uint64_t id = 0;
  if (!ReadIPDLParam(aReader, aActor, &id)) {
    return false;
  }

  if (id == 0) {
    *aResult = nullptr;
  } else if (RefPtr<dom::WindowContext> wc = dom::WindowContext::GetById(id)) {
    *aResult = std::move(wc);
  } else {
    aResult->SetDiscarded(id);
  }
  return true;
}

void IPDLParamTraits<dom::WindowContext::IPCInitializer>::Write(
    IPC::MessageWriter* aWriter, IProtocol* aActor,
    const dom::WindowContext::IPCInitializer& aInit) {
  // Write actor ID parameters.
  WriteIPDLParam(aWriter, aActor, aInit.mInnerWindowId);
  WriteIPDLParam(aWriter, aActor, aInit.mOuterWindowId);
  WriteIPDLParam(aWriter, aActor, aInit.mBrowsingContextId);
  WriteIPDLParam(aWriter, aActor, aInit.mFields);
}

bool IPDLParamTraits<dom::WindowContext::IPCInitializer>::Read(
    IPC::MessageReader* aReader, IProtocol* aActor,
    dom::WindowContext::IPCInitializer* aInit) {
  // Read actor ID parameters.
  return ReadIPDLParam(aReader, aActor, &aInit->mInnerWindowId) &&
         ReadIPDLParam(aReader, aActor, &aInit->mOuterWindowId) &&
         ReadIPDLParam(aReader, aActor, &aInit->mBrowsingContextId) &&
         ReadIPDLParam(aReader, aActor, &aInit->mFields);
}

template struct IPDLParamTraits<dom::WindowContext::BaseTransaction>;

}  // namespace ipc
}  // namespace mozilla
