gecko-dev/docshell/base/WindowContext.cpp

649 строки
22 KiB
C++

/* -*- 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 "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_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)) ==
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();
}
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