Bug 1571063 - Simplify BrowsingContext field sync logic, r=farre
authorNika Layzell <nika@thelayzells.com>
Fri, 09 Aug 2019 14:51:30 +0000
changeset 487193 777ff87ba07a9e153867e4251c2ecf6095e41823
parent 487192 65d774a64533b15a6baee649364f5331a9241a0f
child 487194 fb211e2c4a613621fa0ec5d81a22b4de58918180
push id36413
push usercsabou@mozilla.com
push dateFri, 09 Aug 2019 21:56:59 +0000
treeherdermozilla-central@2909b0a1eb06 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfarre
bugs1571063
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1571063 - Simplify BrowsingContext field sync logic, r=farre Differential Revision: https://phabricator.services.mozilla.com/D40524
docshell/base/BrowsingContext.cpp
docshell/base/BrowsingContext.h
docshell/base/BrowsingContextFieldList.h
docshell/base/CanonicalBrowsingContext.cpp
docshell/base/CanonicalBrowsingContext.h
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -846,67 +846,82 @@ void BrowsingContext::PostMessageMoz(JSC
                                      const WindowPostMessageOptions& aOptions,
                                      nsIPrincipal& aSubjectPrincipal,
                                      ErrorResult& aError) {
   PostMessageMoz(aCx, aMessage, aOptions.mTargetOrigin, aOptions.mTransfer,
                  aSubjectPrincipal, aError);
 }
 
 void BrowsingContext::Transaction::Commit(BrowsingContext* aBrowsingContext) {
+  if (!Validate(aBrowsingContext, nullptr)) {
+    MOZ_CRASH("Cannot commit invalid BrowsingContext transaction");
+  }
+
   if (XRE_IsContentProcess()) {
+    ContentChild* cc = ContentChild::GetSingleton();
+
     // Increment the field epoch for fields affected by this transaction. We
     // only need to do this in content.
-#define MOZ_BC_FIELD_RACY(name, ...)          \
-  if (m##name) {                              \
-    aBrowsingContext->mFieldEpochs.m##name++; \
+    uint64_t epoch = cc->NextBrowsingContextFieldEpoch();
+#define MOZ_BC_FIELD(name, ...)             \
+  if (m##name) {                            \
+    aBrowsingContext->mEpochs.name = epoch; \
   }
-#define MOZ_BC_FIELD(...) /* nothing */
 #include "mozilla/dom/BrowsingContextFieldList.h"
 
-    ContentChild* cc = ContentChild::GetSingleton();
-    cc->SendCommitBrowsingContextTransaction(aBrowsingContext, *this,
-                                             aBrowsingContext->mFieldEpochs);
+    cc->SendCommitBrowsingContextTransaction(aBrowsingContext, *this, epoch);
   } else {
     MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
 
     aBrowsingContext->Group()->EachParent([&](ContentParent* aParent) {
-      const FieldEpochs& childEpochs =
-          aBrowsingContext->Canonical()->GetFieldEpochsForChild(aParent);
       Unused << aParent->SendCommitBrowsingContextTransaction(
-          aBrowsingContext, *this, childEpochs);
+          aBrowsingContext, *this, aParent->GetBrowsingContextFieldEpoch());
     });
   }
 
-  Apply(aBrowsingContext, nullptr);
+  Apply(aBrowsingContext);
+}
+bool BrowsingContext::Transaction::Validate(BrowsingContext* aBrowsingContext,
+                                            ContentParent* aSource) {
+#define MOZ_BC_FIELD(name, ...)                                        \
+  if (m##name && !aBrowsingContext->MaySet##name(*m##name, aSource)) { \
+    NS_WARNING("Invalid attempt to set BC field " #name);              \
+    return false;                                                      \
+  }
+#include "mozilla/dom/BrowsingContextFieldList.h"
+
+  mValidated = true;
+  return true;
 }
 
-void BrowsingContext::Transaction::Apply(BrowsingContext* aBrowsingContext,
-                                         ContentParent* aSource,
-                                         const FieldEpochs* aEpochs) {
-  // Filter out racy fields which have been updated in this process since this
-  // transaction was committed in the parent. This should only ever occur in the
-  // content process.
-  if (aEpochs) {
-    MOZ_ASSERT(XRE_IsContentProcess());
-#define MOZ_BC_FIELD_RACY(name, ...)                                 \
-  if (m##name) {                                                     \
-    if (aEpochs->m##name < aBrowsingContext->mFieldEpochs.m##name) { \
-      m##name.reset();                                               \
-    }                                                                \
+bool BrowsingContext::Transaction::Validate(BrowsingContext* aBrowsingContext,
+                                            ContentParent* aSource,
+                                            uint64_t aEpoch) {
+  MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess(),
+                        "Should only be called in content process");
+
+  // Clear fields which are obsoleted by the epoch.
+#define MOZ_BC_FIELD(name, ...)                             \
+  if (m##name && aBrowsingContext->mEpochs.name > aEpoch) { \
+    m##name.reset();                                        \
   }
-#define MOZ_BC_FIELD(...) /* nothing */
 #include "mozilla/dom/BrowsingContextFieldList.h"
-  }
+
+  return Validate(aBrowsingContext, aSource);
+}
 
-#define MOZ_BC_FIELD(name, ...)                         \
-  if (m##name) {                                        \
-    aBrowsingContext->WillSet##name(*m##name, aSource); \
-    aBrowsingContext->m##name = std::move(*m##name);    \
-    aBrowsingContext->DidSet##name(aSource);            \
-    m##name.reset();                                    \
+void BrowsingContext::Transaction::Apply(BrowsingContext* aBrowsingContext) {
+  MOZ_RELEASE_ASSERT(mValidated,
+                     "Must validate BrowsingContext Transaction before Apply");
+
+#define MOZ_BC_FIELD(name, ...)                      \
+  if (m##name) {                                     \
+    aBrowsingContext->m##name = std::move(*m##name); \
+    aBrowsingContext->DidSet##name();                \
+    m##name.reset();                                 \
   }
 #include "mozilla/dom/BrowsingContextFieldList.h"
 }
 
 BrowsingContext::IPCInitializer BrowsingContext::GetIPCInitializer() {
   // FIXME: We should assert that we're loaded in-content here. (bug 1553804)
 
   IPCInitializer init;
@@ -963,17 +978,17 @@ void BrowsingContext::StartDelayedAutopl
   if (!mDocShell) {
     return;
   }
   AUTOPLAY_LOG("%s : StartDelayedAutoplayMediaComponents for bc 0x%08" PRIx64,
                XRE_IsParentProcess() ? "Parent" : "Child", Id());
   mDocShell->StartDelayedAutoplayMediaComponents();
 }
 
-void BrowsingContext::DidSetIsActivatedByUserGesture(ContentParent* aSource) {
+void BrowsingContext::DidSetIsActivatedByUserGesture() {
   MOZ_ASSERT(!mParent, "Set user activation flag on non top-level context!");
   USER_ACTIVATION_LOG(
       "Set user gesture activation %d for %s browsing context 0x%08" PRIx64,
       mIsActivatedByUserGesture, XRE_IsParentProcess() ? "Parent" : "Child",
       Id());
 }
 
 }  // namespace dom
@@ -1038,54 +1053,39 @@ bool IPDLParamTraits<dom::BrowsingContex
 
   *aResult = browsingContext.forget();
   return true;
 }
 
 void IPDLParamTraits<dom::BrowsingContext::Transaction>::Write(
     IPC::Message* aMessage, IProtocol* aActor,
     const dom::BrowsingContext::Transaction& aTransaction) {
+  MOZ_RELEASE_ASSERT(
+      aTransaction.mValidated,
+      "Must validate BrowsingContext Transaction before sending");
+
 #define MOZ_BC_FIELD(name, ...) \
   WriteIPDLParam(aMessage, aActor, aTransaction.m##name);
 #include "mozilla/dom/BrowsingContextFieldList.h"
 }
 
 bool IPDLParamTraits<dom::BrowsingContext::Transaction>::Read(
     const IPC::Message* aMessage, PickleIterator* aIterator, IProtocol* aActor,
     dom::BrowsingContext::Transaction* aTransaction) {
+  aTransaction->mValidated = false;
+
 #define MOZ_BC_FIELD(name, ...)                                              \
   if (!ReadIPDLParam(aMessage, aIterator, aActor, &aTransaction->m##name)) { \
     return false;                                                            \
   }
 #include "mozilla/dom/BrowsingContextFieldList.h"
 
   return true;
 }
 
-void IPDLParamTraits<dom::BrowsingContext::FieldEpochs>::Write(
-    IPC::Message* aMessage, IProtocol* aActor,
-    const dom::BrowsingContext::FieldEpochs& aEpochs) {
-#define MOZ_BC_FIELD_RACY(name, ...) \
-  WriteIPDLParam(aMessage, aActor, aEpochs.m##name);
-#define MOZ_BC_FIELD(...) /* nothing */
-#include "mozilla/dom/BrowsingContextFieldList.h"
-}
-
-bool IPDLParamTraits<dom::BrowsingContext::FieldEpochs>::Read(
-    const IPC::Message* aMessage, PickleIterator* aIterator, IProtocol* aActor,
-    dom::BrowsingContext::FieldEpochs* aEpochs) {
-#define MOZ_BC_FIELD_RACY(name, ...)                                    \
-  if (!ReadIPDLParam(aMessage, aIterator, aActor, &aEpochs->m##name)) { \
-    return false;                                                       \
-  }
-#define MOZ_BC_FIELD(...) /* nothing */
-#include "mozilla/dom/BrowsingContextFieldList.h"
-  return true;
-}
-
 void IPDLParamTraits<dom::BrowsingContext::IPCInitializer>::Write(
     IPC::Message* aMessage, IProtocol* aActor,
     const dom::BrowsingContext::IPCInitializer& aInit) {
   // Write actor ID parameters.
   WriteIPDLParam(aMessage, aActor, aInit.mId);
   WriteIPDLParam(aMessage, aActor, aInit.mParentId);
 
   WriteIPDLParam(aMessage, aActor, aInit.mCached);
--- a/docshell/base/BrowsingContext.h
+++ b/docshell/base/BrowsingContext.h
@@ -63,18 +63,20 @@ class BrowsingContextBase {
 #include "mozilla/dom/BrowsingContextFieldList.h"
   }
   ~BrowsingContextBase() = default;
 
 #define MOZ_BC_FIELD(name, type)                                    \
   type m##name;                                                     \
                                                                     \
   /* shadow to validate fields. aSource is setter process or null*/ \
-  void WillSet##name(type const& aValue, ContentParent* aSource) {} \
-  void DidSet##name(ContentParent* aSource) {}
+  bool MaySet##name(type const& aValue, ContentParent* aSource) {   \
+    return true;                                                    \
+  }                                                                 \
+  void DidSet##name() {}
 #include "mozilla/dom/BrowsingContextFieldList.h"
 };
 
 // BrowsingContext, in this context, is the cross process replicated
 // environment in which information about documents is stored. In
 // particular the tree structure of nested browsing contexts is
 // represented by the tree of BrowsingContexts.
 //
@@ -287,63 +289,52 @@ class BrowsingContext : public nsWrapper
                                        JSStructuredCloneReader* aReader,
                                        StructuredCloneHolder* aHolder);
   bool WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter,
                             StructuredCloneHolder* aHolder);
 
   void StartDelayedAutoplayMediaComponents();
 
   /**
-   * Each synced racy field in a BrowsingContext needs to have a epoch value
-   * which is used to resolve race conflicts by ensuring that only the last
-   * message received in the parent process wins.
-   */
-  struct FieldEpochs {
-#define MOZ_BC_FIELD(...) /* nothing */
-#define MOZ_BC_FIELD_RACY(name, ...) uint64_t m##name = 0;
-#include "mozilla/dom/BrowsingContextFieldList.h"
-  };
-
-  /**
    * Transaction object. This object is used to specify and then commit
    * modifications to synchronized fields in BrowsingContexts.
    */
   class Transaction {
    public:
     // Apply the changes from this transaction to the specified BrowsingContext
-    // in all processes. This method will call the correct `WillSet` and
+    // in all processes. This method will call the correct `MaySet` and
     // `DidSet` methods, as well as move the value.
     //
     // NOTE: This method mutates `this`, resetting all members to `Nothing()`
     void Commit(BrowsingContext* aOwner);
 
-    // You probably don't want to directly call this method - instead call
-    // `Commit`, which will perform the necessary synchronization.
+    // This method should be called before invoking `Apply` on this transaction
+    // object.
     //
     // |aSource| is the ContentParent which is performing the mutation in the
     // parent process.
-    void Apply(BrowsingContext* aOwner, ContentParent* aSource,
-               const FieldEpochs* aEpochs = nullptr);
+    MOZ_MUST_USE bool Validate(BrowsingContext* aOwner, ContentParent* aSource,
+                               uint64_t aEpoch);
+    MOZ_MUST_USE bool Validate(BrowsingContext* aOwner, ContentParent* aSource);
 
-    bool HasNonRacyField() const {
-#define MOZ_BC_FIELD(name, ...) \
-  if (m##name.isSome()) {       \
-    return true;                \
-  }
-#define MOZ_BC_FIELD_RACY(...) /* nothing */
-#include "mozilla/dom/BrowsingContextFieldList.h"
-
-      return false;
-    }
+    // You probably don't want to directly call this method - instead call
+    // `Commit`, which will perform the necessary synchronization.
+    //
+    // `Validate` must be called before calling this method.
+    void Apply(BrowsingContext* aOwner);
 
 #define MOZ_BC_FIELD(name, type) mozilla::Maybe<type> m##name;
 #include "mozilla/dom/BrowsingContextFieldList.h"
 
    private:
     friend struct mozilla::ipc::IPDLParamTraits<Transaction>;
+
+    // Has `Validate` been called on this method yet?
+    // NOTE: This field is not synced, and must be called in every process.
+    bool mValidated = false;
   };
 
 #define MOZ_BC_FIELD(name, type)                        \
   template <typename... Args>                           \
   void Set##name(Args&&... aValue) {                    \
     Transaction txn;                                    \
     txn.m##name.emplace(std::forward<Args>(aValue)...); \
     txn.Commit(this);                                   \
@@ -444,25 +435,26 @@ class BrowsingContext : public nsWrapper
     friend class RemoteLocationProxy;
     BrowsingContext* GetBrowsingContext() {
       return reinterpret_cast<BrowsingContext*>(
           uintptr_t(this) - offsetof(BrowsingContext, mLocation));
     }
   };
 
   // Ensure that opener is in the same BrowsingContextGroup.
-  void WillSetOpener(const uint64_t& aValue, ContentParent* aSource) {
+  bool MaySetOpener(const uint64_t& aValue, ContentParent* aSource) {
     if (aValue != 0) {
       RefPtr<BrowsingContext> opener = Get(aValue);
-      MOZ_RELEASE_ASSERT(opener && opener->Group() == Group());
+      return opener && opener->Group() == Group();
     }
+    return true;
   }
 
   // Ensure that we only set the flag on the top level browsing context.
-  void DidSetIsActivatedByUserGesture(ContentParent* aSource);
+  void DidSetIsActivatedByUserGesture();
 
   // Type of BrowsingContent
   const Type mType;
 
   // Unique id identifying BrowsingContext
   const uint64_t mBrowsingContextId;
 
   RefPtr<BrowsingContextGroup> mGroup;
@@ -474,17 +466,27 @@ class BrowsingContext : public nsWrapper
 
   // This is not a strong reference, but using a JS::Heap for that should be
   // fine. The JSObject stored in here should be a proxy with a
   // nsOuterWindowProxy handler, which will update the pointer from its
   // objectMoved hook and clear it from its finalize hook.
   JS::Heap<JSObject*> mWindowProxy;
   LocationProxy mLocation;
 
-  FieldEpochs mFieldEpochs;
+  // Whenever a `Transaction` is committed, it is associated with a new
+  // "Browsing Context Epoch". The epoch is associated with a specific content
+  // process. This `mEpochs` field tracks the epoch of the most recent comitted
+  // transaction in this process, and is used to resolve races between processes
+  // and ensure browsing context field consistency.
+  //
+  // This field is only used by content processes.
+  struct {
+#define MOZ_BC_FIELD(name, ...) uint64_t name = 0;
+#include "mozilla/dom/BrowsingContextFieldList.h"
+  } mEpochs;
 
   // Is the most recent Document in this BrowsingContext loaded within this
   // process? This may be true with a null mDocShell after the Window has been
   // closed.
   bool mIsInProcess : 1;
 
   // Has this browsing context been discarded? BrowsingContexts should
   // only be discarded once.
@@ -504,17 +506,16 @@ class BrowsingContext : public nsWrapper
  * transplanted onto it. Therefore it should be used as the value in the remote
  * proxy map.
  */
 extern bool GetRemoteOuterWindowProxy(JSContext* aCx, BrowsingContext* aContext,
                                       JS::Handle<JSObject*> aTransplantTo,
                                       JS::MutableHandle<JSObject*> aRetVal);
 
 typedef BrowsingContext::Transaction BrowsingContextTransaction;
-typedef BrowsingContext::FieldEpochs BrowsingContextFieldEpochs;
 typedef BrowsingContext::IPCInitializer BrowsingContextInitializer;
 typedef BrowsingContext::Children BrowsingContextChildren;
 
 }  // namespace dom
 
 // Allow sending BrowsingContext objects over IPC.
 namespace ipc {
 template <>
@@ -531,26 +532,16 @@ struct IPDLParamTraits<dom::BrowsingCont
                     const dom::BrowsingContext::Transaction& aTransaction);
 
   static bool Read(const IPC::Message* aMessage, PickleIterator* aIterator,
                    IProtocol* aActor,
                    dom::BrowsingContext::Transaction* aTransaction);
 };
 
 template <>
-struct IPDLParamTraits<dom::BrowsingContext::FieldEpochs> {
-  static void Write(IPC::Message* aMessage, IProtocol* aActor,
-                    const dom::BrowsingContext::FieldEpochs& aEpochs);
-
-  static bool Read(const IPC::Message* aMessage, PickleIterator* aIterator,
-                   IProtocol* aActor,
-                   dom::BrowsingContext::FieldEpochs* aEpochs);
-};
-
-template <>
 struct IPDLParamTraits<dom::BrowsingContext::IPCInitializer> {
   static void Write(IPC::Message* aMessage, IProtocol* aActor,
                     const dom::BrowsingContext::IPCInitializer& aInitializer);
 
   static bool Read(const IPC::Message* aMessage, PickleIterator* aIterator,
                    IProtocol* aActor,
                    dom::BrowsingContext::IPCInitializer* aInitializer);
 };
--- a/docshell/base/BrowsingContextFieldList.h
+++ b/docshell/base/BrowsingContextFieldList.h
@@ -3,29 +3,24 @@
 /* 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/. */
 
 // Fields are, by default, settable by any process and readable by any process.
 // Racy sets will be resolved as-if they occurred in the order the parent
 // process finds out about them.
 //
-// Process restrictions on racy fields may be added in `WillSet{name}`
-// validators.
-#ifndef MOZ_BC_FIELD_RACY
-#  define MOZ_BC_FIELD_RACY MOZ_BC_FIELD
-#endif
-
-MOZ_BC_FIELD_RACY(Name, nsString)
-MOZ_BC_FIELD_RACY(Closed, bool)
+// Process restrictions may be added by declaring a method `MaySet{name}` on
+// `BrowsingContext`.
+MOZ_BC_FIELD(Name, nsString)
+MOZ_BC_FIELD(Closed, bool)
 MOZ_BC_FIELD(EmbedderPolicy, nsILoadInfo::CrossOriginEmbedderPolicy)
 MOZ_BC_FIELD(OpenerPolicy, nsILoadInfo::CrossOriginOpenerPolicy)
 
 // The current opener for this BrowsingContext. This is a weak reference, and
 // stored as the opener ID.
 MOZ_BC_FIELD(OpenerId, uint64_t)
 
 // Toplevel browsing contexts only. This field controls whether the browsing
 // context is currently considered to be activated by a gesture.
-MOZ_BC_FIELD_RACY(IsActivatedByUserGesture, bool)
+MOZ_BC_FIELD(IsActivatedByUserGesture, bool)
 
 #undef MOZ_BC_FIELD
-#undef MOZ_BC_FIELD_RACY
--- a/docshell/base/CanonicalBrowsingContext.cpp
+++ b/docshell/base/CanonicalBrowsingContext.cpp
@@ -131,41 +131,16 @@ void CanonicalBrowsingContext::SetEmbedd
   if (RefPtr<BrowsingContext> parent = GetParent()) {
     MOZ_RELEASE_ASSERT(aGlobal->BrowsingContext() == parent,
                        "Embedder has incorrect browsing context");
   }
 
   mEmbedderWindowGlobal = aGlobal;
 }
 
-bool CanonicalBrowsingContext::ValidateTransaction(
-    const Transaction& aTransaction, ContentParent* aProcess) {
-  if (MOZ_LOG_TEST(GetLog(), LogLevel::Debug)) {
-#define MOZ_BC_FIELD(name, ...)                                               \
-  if (aTransaction.m##name.isSome()) {                                        \
-    MOZ_LOG(GetLog(), LogLevel::Debug,                                        \
-            ("Validate Transaction 0x%08" PRIx64 " set " #name                \
-             " (from: 0x%08" PRIx64 " owner: 0x%08" PRIx64 ")",               \
-             Id(), aProcess ? static_cast<uint64_t>(aProcess->ChildID()) : 0, \
-             mProcessId));                                                    \
-  }
-#include "mozilla/dom/BrowsingContextFieldList.h"
-  }
-
-  // Check that the correct process is performing sets for transactions with
-  // non-racy fields.
-  if (aTransaction.HasNonRacyField()) {
-    if (NS_WARN_IF(aProcess && mProcessId != aProcess->ChildID())) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
 JSObject* CanonicalBrowsingContext::WrapObject(
     JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
   return CanonicalBrowsingContext_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 void CanonicalBrowsingContext::Traverse(
     nsCycleCollectionTraversalCallback& cb) {
   CanonicalBrowsingContext* tmp = this;
@@ -214,25 +189,10 @@ void CanonicalBrowsingContext::UpdateMed
   if (window) {
     window->UpdateMediaAction(aAction);
   }
   Group()->EachParent([&](ContentParent* aParent) {
     Unused << aParent->SendUpdateMediaAction(this, aAction);
   });
 }
 
-void CanonicalBrowsingContext::SetFieldEpochsForChild(
-    ContentParent* aChild, const BrowsingContext::FieldEpochs& aEpochs) {
-  mChildFieldEpochs.Put(aChild->ChildID(), aEpochs);
-}
-
-const BrowsingContext::FieldEpochs&
-CanonicalBrowsingContext::GetFieldEpochsForChild(ContentParent* aChild) {
-  static const BrowsingContext::FieldEpochs sDefaultFieldEpochs;
-
-  if (auto entry = mChildFieldEpochs.Lookup(aChild->ChildID())) {
-    return entry.Data();
-  }
-  return sDefaultFieldEpochs;
-}
-
 }  // namespace dom
 }  // namespace mozilla
--- a/docshell/base/CanonicalBrowsingContext.h
+++ b/docshell/base/CanonicalBrowsingContext.h
@@ -78,25 +78,16 @@ class CanonicalBrowsingContext final : p
   // set the media mute property for the top level window and propagate it to
   // other top level windows in other processes.
   void NotifyMediaMutedChanged(bool aMuted);
 
   // This function would update the media action for the current outer window
   // and propogate the action to other browsing contexts in content processes.
   void UpdateMediaAction(MediaControlActions aAction);
 
-  // Validate that the given process is allowed to perform the given
-  // transaction. aSource is |nullptr| if set in the parent process.
-  bool ValidateTransaction(const Transaction& aTransaction,
-                           ContentParent* aSource);
-
-  void SetFieldEpochsForChild(ContentParent* aChild,
-                              const FieldEpochs& aEpochs);
-  const FieldEpochs& GetFieldEpochsForChild(ContentParent* aChild);
-
  protected:
   void Traverse(nsCycleCollectionTraversalCallback& cb);
   void Unlink();
 
   using Type = BrowsingContext::Type;
   CanonicalBrowsingContext(BrowsingContext* aParent,
                            BrowsingContextGroup* aGroup,
                            uint64_t aBrowsingContextId, uint64_t aProcessId,
@@ -108,18 +99,14 @@ class CanonicalBrowsingContext final : p
   // XXX(farre): Store a ContentParent pointer here rather than mProcessId?
   // Indicates which process owns the docshell.
   uint64_t mProcessId;
 
   // All live window globals within this browsing context.
   nsTHashtable<nsRefPtrHashKey<WindowGlobalParent>> mWindowGlobals;
   RefPtr<WindowGlobalParent> mCurrentWindowGlobal;
   RefPtr<WindowGlobalParent> mEmbedderWindowGlobal;
-
-  // Generation information for each content process which has interacted with
-  // this CanonicalBrowsingContext, by ChildID.
-  nsDataHashtable<nsUint64HashKey, FieldEpochs> mChildFieldEpochs;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // !defined(mozilla_dom_CanonicalBrowsingContext_h)
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -4000,20 +4000,28 @@ mozilla::ipc::IPCResult ContentChild::Re
   event->UnpackFrom(aMessage);
 
   window->Dispatch(TaskCategory::Other, event.forget());
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentChild::RecvCommitBrowsingContextTransaction(
     BrowsingContext* aContext, BrowsingContext::Transaction&& aTransaction,
-    BrowsingContext::FieldEpochs&& aEpochs) {
-  if (aContext) {
-    aTransaction.Apply(aContext, nullptr, &aEpochs);
+    uint64_t aEpoch) {
+  if (!aContext || aContext->IsDiscarded()) {
+    MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
+            ("ChildIPC: Trying to send a message to dead or detached context"));
+    return IPC_OK();
   }
+
+  if (!aTransaction.Validate(aContext, nullptr, aEpoch)) {
+    return IPC_FAIL(this, "Invalid BrowsingContext transaction from Parent");
+  }
+
+  aTransaction.Apply(aContext);
   return IPC_OK();
 }
 
 void ContentChild::HoldBrowsingContextGroup(BrowsingContextGroup* aBCG) {
   mBrowsingContextGroupHolder.AppendElement(aBCG);
 }
 
 void ContentChild::ReleaseBrowsingContextGroup(BrowsingContextGroup* aBCG) {
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -677,16 +677,25 @@ class ContentChild final : public PConte
                                             bool aMuted);
 
   mozilla::ipc::IPCResult RecvUpdateMediaAction(BrowsingContext* aContext,
                                                 MediaControlActions aAction);
 
   void HoldBrowsingContextGroup(BrowsingContextGroup* aBCG);
   void ReleaseBrowsingContextGroup(BrowsingContextGroup* aBCG);
 
+  // See `BrowsingContext::mEpochs` for an explanation of this field.
+  uint64_t GetBrowsingContextFieldEpoch() const {
+    return mBrowsingContextFieldEpoch;
+  }
+  uint64_t NextBrowsingContextFieldEpoch() {
+    mBrowsingContextFieldEpoch++;
+    return mBrowsingContextFieldEpoch;
+  }
+
 #ifdef NIGHTLY_BUILD
   // Fetch the current number of pending input events.
   //
   // NOTE: This method performs an atomic read, and is safe to call from all
   // threads.
   uint32_t GetPendingInputEvents() { return mPendingInputEvents; }
 #endif
 
@@ -728,17 +737,17 @@ class ContentChild final : public PConte
   mozilla::ipc::IPCResult RecvWindowFocus(BrowsingContext* aContext);
   mozilla::ipc::IPCResult RecvWindowBlur(BrowsingContext* aContext);
   mozilla::ipc::IPCResult RecvWindowPostMessage(
       BrowsingContext* aContext, const ClonedMessageData& aMessage,
       const PostMessageData& aData);
 
   mozilla::ipc::IPCResult RecvCommitBrowsingContextTransaction(
       BrowsingContext* aContext, BrowsingContext::Transaction&& aTransaction,
-      BrowsingContext::FieldEpochs&& aEpochs);
+      uint64_t aEpoch);
 
 #ifdef NIGHTLY_BUILD
   virtual PContentChild::Result OnMessageReceived(const Message& aMsg) override;
 #else
   using PContentChild::OnMessageReceived;
 #endif
 
   virtual PContentChild::Result OnMessageReceived(const Message& aMsg,
@@ -820,15 +829,18 @@ class ContentChild final : public PConte
   // off-main-thread.
   mozilla::Atomic<uint32_t> mPendingInputEvents;
 #endif
 
   uint32_t mNetworkLinkType = 0;
 
   nsTArray<RefPtr<BrowsingContextGroup>> mBrowsingContextGroupHolder;
 
+  // See `BrowsingContext::mEpochs` for an explanation of this field.
+  uint64_t mBrowsingContextFieldEpoch = 0;
+
   DISALLOW_EVIL_CONSTRUCTORS(ContentChild);
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_ContentChild_h
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -6017,39 +6017,43 @@ void ContentParent::OnBrowsingContextGro
 void ContentParent::OnBrowsingContextGroupUnsubscribe(
     BrowsingContextGroup* aGroup) {
   MOZ_DIAGNOSTIC_ASSERT(aGroup);
   mGroups.RemoveEntry(aGroup);
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvCommitBrowsingContextTransaction(
     BrowsingContext* aContext, BrowsingContext::Transaction&& aTransaction,
-    BrowsingContext::FieldEpochs&& aEpochs) {
+    uint64_t aEpoch) {
+  // Record the new BrowsingContextFieldEpoch associated with this transaction.
+  // This should be done unconditionally, so that we're always in-sync.
+  //
+  // The order the parent process receives transactions is considered the
+  // "canonical" ordering, so we don't need to worry about doing any
+  // epoch-related validation.
+  MOZ_ASSERT(aEpoch == mBrowsingContextFieldEpoch + 1,
+             "Child process skipped an epoch?");
+  mBrowsingContextFieldEpoch = aEpoch;
+
   if (!aContext || aContext->IsDiscarded()) {
     MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Warning,
             ("ParentIPC: Trying to run transaction on missing context."));
     return IPC_OK();
   }
 
-  // Check if the transaction is valid.
-  if (!aContext->Canonical()->ValidateTransaction(aTransaction, this)) {
-    MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Error,
-            ("ParentIPC: Trying to run invalid transaction."));
-    return IPC_FAIL_NO_REASON(this);
+  if (!aTransaction.Validate(aContext, this)) {
+    return IPC_FAIL(this, "Invalid BrowsingContext transaction from Child");
   }
 
   aContext->Group()->EachOtherParent(this, [&](ContentParent* aParent) {
     Unused << aParent->SendCommitBrowsingContextTransaction(
-        aContext, aTransaction,
-        aContext->Canonical()->GetFieldEpochsForChild(aParent));
+        aContext, aTransaction, aParent->GetBrowsingContextFieldEpoch());
   });
 
-  aTransaction.Apply(aContext, this);
-  aContext->Canonical()->SetFieldEpochsForChild(this, aEpochs);
-
+  aTransaction.Apply(aContext);
   return IPC_OK();
 }
 }  // namespace dom
 }  // namespace mozilla
 
 NS_IMPL_ISUPPORTS(ParentIdleListener, nsIObserver)
 
 NS_IMETHODIMP
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1025,17 +1025,17 @@ class ContentParent final : public PCont
       const bool& aIsFromChromeContext,
       const ClonedMessageData* aStack = nullptr);
 
  public:
   mozilla::ipc::IPCResult RecvPrivateDocShellsExist(const bool& aExist);
 
   mozilla::ipc::IPCResult RecvCommitBrowsingContextTransaction(
       BrowsingContext* aContext, BrowsingContext::Transaction&& aTransaction,
-      BrowsingContext::FieldEpochs&& aEpochs);
+      uint64_t aEpoch);
 
   mozilla::ipc::IPCResult RecvFirstIdle();
 
   mozilla::ipc::IPCResult RecvDeviceReset();
 
   mozilla::ipc::IPCResult RecvKeywordToURI(const nsCString& aKeyword,
                                            nsString* aProviderName,
                                            RefPtr<nsIInputStream>* aPostData,
@@ -1191,16 +1191,21 @@ class ContentParent final : public PCont
 
   bool IsRecordingOrReplaying() const {
     return mRecordReplayState != eNotRecordingOrReplaying;
   }
 
   void OnBrowsingContextGroupSubscribe(BrowsingContextGroup* aGroup);
   void OnBrowsingContextGroupUnsubscribe(BrowsingContextGroup* aGroup);
 
+  // See `BrowsingContext::mEpochs` for an explanation of this field.
+  uint64_t GetBrowsingContextFieldEpoch() const {
+    return mBrowsingContextFieldEpoch;
+  }
+
   void UpdateNetworkLinkType();
 
   static bool ShouldSyncPreference(const char16_t* aData);
 
  private:
   // Released in ActorDealloc; deliberately not exposed to the CC.
   RefPtr<ContentParent> mSelfRef;
 
@@ -1333,16 +1338,19 @@ class ContentParent final : public PCont
 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
   // When set to true, indicates that content processes should
   // initialize their sandbox during startup instead of waiting
   // for the SetProcessSandbox IPDL message.
   static bool sEarlySandboxInit;
 #endif
 
   nsTHashtable<nsRefPtrHashKey<BrowsingContextGroup>> mGroups;
+
+  // See `BrowsingContext::mEpochs` for an explanation of this field.
+  uint64_t mBrowsingContextFieldEpoch = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(ContentParent, NS_CONTENTPARENT_IID)
 
 const nsDependentSubstring RemoteTypePrefix(
     const nsAString& aContentProcessType);
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -95,17 +95,16 @@ using mozilla::Telemetry::ScalarAction f
 using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::DynamicScalarDefinition from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::ChildEventData from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::DiscardedData from "mozilla/TelemetryComms.h";
 using mozilla::CrossProcessMutexHandle from "mozilla/ipc/CrossProcessMutex.h";
 using refcounted class nsIInputStream from "mozilla/ipc/IPCStreamUtils.h";
 using refcounted class mozilla::dom::BrowsingContext from "mozilla/dom/BrowsingContext.h";
 using mozilla::dom::BrowsingContextTransaction from "mozilla/dom/BrowsingContext.h";
-using mozilla::dom::BrowsingContextFieldEpochs from "mozilla/dom/BrowsingContext.h";
 using mozilla::dom::BrowsingContextInitializer from "mozilla/dom/BrowsingContext.h";
 using base::SharedMemoryHandle from "base/shared_memory.h";
 using mozilla::ipc::SharedMemoryBasic::Handle from "mozilla/ipc/SharedMemoryBasic.h";
 using mozilla::fontlist::Pointer from "SharedFontList.h";
 using gfxSparseBitSet from "gfxFontUtils.h";
 using mozilla::dom::MediaControlActions from "ipc/MediaControlIPC.h";
 
 union ChromeRegistryItem
@@ -1402,17 +1401,17 @@ parent:
     * When media became audible or inaudible in content process, we have to
     * notify chrome process in order to which tab is audible.
     */
     async NotifyMediaAudibleChanged(BrowsingContext aContext, bool aAudible);
 
 both:
     async CommitBrowsingContextTransaction(BrowsingContext aContext,
                                            BrowsingContextTransaction aTransaction,
-                                           BrowsingContextFieldEpochs aEpochs);
+                                           uint64_t aEpoch);
 
     async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
                        Principal aPrincipal, ClonedMessageData aData);
 
     /**
      * Notify `push-subscription-modified` observers in the parent and child.
      */
     async NotifyPushSubscriptionModifiedObservers(nsCString scope,