Bug 1532661 - Part 4: Support initializing complete BrowsingContextGroups in a single op with an IPCInitializer struct, r=farre
authorNika Layzell <nika@thelayzells.com>
Thu, 14 Mar 2019 18:51:09 +0000
changeset 464044 9ce1a2365afff092f9107a5f1ae1eca6a34dfce3
parent 464043 58f77fa35eeab8de03c52f316d9f0553685a5f8d
child 464045 856a3d16a4ca856711ad9e6368cccd5aa1e22118
push id35707
push userrmaries@mozilla.com
push dateFri, 15 Mar 2019 03:42:43 +0000
treeherdermozilla-central@5ce27c44f79e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfarre
bugs1532661
milestone67.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 1532661 - Part 4: Support initializing complete BrowsingContextGroups in a single op with an IPCInitializer struct, r=farre Depends on D22192 Differential Revision: https://phabricator.services.mozilla.com/D22193
docshell/base/BrowsingContext.cpp
docshell/base/BrowsingContext.h
docshell/base/BrowsingContextGroup.cpp
docshell/base/BrowsingContextGroup.h
docshell/base/CanonicalBrowsingContext.cpp
docshell/base/CanonicalBrowsingContext.h
dom/base/nsFrameLoader.cpp
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/ipc/tests/test_force_oop_iframe.html
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -102,88 +102,97 @@ already_AddRefed<BrowsingContext> Browsi
   MOZ_DIAGNOSTIC_ASSERT(!aParent || aParent->mType == aType);
 
   uint64_t id = nsContentUtils::GenerateBrowsingContextId();
 
   MOZ_LOG(GetLog(), LogLevel::Debug,
           ("Creating 0x%08" PRIx64 " in %s", id,
            XRE_IsParentProcess() ? "Parent" : "Child"));
 
+  // Determine which BrowsingContextGroup this context should be created in.
+  RefPtr<BrowsingContextGroup> group =
+      BrowsingContextGroup::Select(aParent, aOpener);
+
   RefPtr<BrowsingContext> context;
   if (XRE_IsParentProcess()) {
-    context = new CanonicalBrowsingContext(aParent, aOpener, aName, id,
+    context = new CanonicalBrowsingContext(aParent, group, id,
                                            /* aProcessId */ 0, aType);
   } else {
-    context = new BrowsingContext(aParent, aOpener, aName, id, aType);
+    context = new BrowsingContext(aParent, group, id, aType);
+  }
+
+  // The name and opener fields need to be explicitly initialized. Don't bother
+  // using transactions to set them, as we haven't been attached yet.
+  context->mName = aName;
+  context->mOpener = aOpener;
+
+  if (aParent) {
+    context->mCrossOriginPolicy = aParent->mCrossOriginPolicy;
+  } else if (aOpener) {
+    context->mCrossOriginPolicy = aOpener->mCrossOriginPolicy;
+  } else {
+    context->mCrossOriginPolicy = nsILoadInfo::CROSS_ORIGIN_POLICY_NULL;
   }
 
   Register(context);
 
   // Attach the browsing context to the tree.
   context->Attach();
 
   return context.forget();
 }
 
 /* static */
 already_AddRefed<BrowsingContext> BrowsingContext::CreateFromIPC(
-    BrowsingContext* aParent, BrowsingContext* aOpener, const nsAString& aName,
-    uint64_t aId, ContentParent* aOriginProcess) {
-  MOZ_DIAGNOSTIC_ASSERT(aOriginProcess || XRE_IsContentProcess(),
-                        "Parent Process IPC contexts need a Content Process.");
-  MOZ_DIAGNOSTIC_ASSERT(!aParent || aParent->IsContent());
+    BrowsingContext::IPCInitializer&& aInit, BrowsingContextGroup* aGroup,
+    ContentParent* aOriginProcess) {
+  MOZ_DIAGNOSTIC_ASSERT(aOriginProcess || XRE_IsContentProcess());
+  MOZ_DIAGNOSTIC_ASSERT(aGroup);
+
+  uint64_t originId = aOriginProcess ? aOriginProcess->ChildID() : 0;
 
   MOZ_LOG(GetLog(), LogLevel::Debug,
-          ("Creating 0x%08" PRIx64 " from IPC (origin=0x%08" PRIx64 ")", aId,
-           aOriginProcess ? uint64_t(aOriginProcess->ChildID()) : 0));
+          ("Creating 0x%08" PRIx64 " from IPC (origin=0x%08" PRIx64 ")",
+           aInit.mId, originId));
+
+  RefPtr<BrowsingContext> parent = aInit.GetParent();
 
   RefPtr<BrowsingContext> context;
   if (XRE_IsParentProcess()) {
-    context = new CanonicalBrowsingContext(
-        aParent, aOpener, aName, aId, aOriginProcess->ChildID(), Type::Content);
-
-    context->Group()->Subscribe(aOriginProcess);
+    context = new CanonicalBrowsingContext(parent, aGroup, aInit.mId, originId,
+                                           Type::Content);
   } else {
-    context = new BrowsingContext(aParent, aOpener, aName, aId, Type::Content);
+    context = new BrowsingContext(parent, aGroup, aInit.mId, Type::Content);
   }
 
   Register(context);
 
+  // Initialize all non-opener fields. We skip the opener here, as when doing
+  // the initial sync of the fields we may not have our opener in this process
+  // yet at this point.
+#define MOZ_BC_FIELD_SKIP_OPENER
+#define MOZ_BC_FIELD(name, ...) context->m##name = aInit.m##name;
+#include "mozilla/dom/BrowsingContextFieldList.h"
+
   // Caller handles attaching us to the tree.
 
   return context.forget();
 }
 
 BrowsingContext::BrowsingContext(BrowsingContext* aParent,
-                                 BrowsingContext* aOpener,
-                                 const nsAString& aName,
+                                 BrowsingContextGroup* aGroup,
                                  uint64_t aBrowsingContextId, Type aType)
-    : mName(aName),
-      mClosed(false),
-      mOpener(aOpener),
-      mIsActivatedByUserGesture(false),
-      mType(aType),
+    : mType(aType),
       mBrowsingContextId(aBrowsingContextId),
+      mGroup(aGroup),
       mParent(aParent),
       mIsInProcess(false) {
-  mCrossOriginPolicy = nsILoadInfo::CROSS_ORIGIN_POLICY_NULL;
-
-  // Specify our group in our constructor. We will explicitly join the group
-  // when we are registered, as doing so will take a reference.
-  if (mParent) {
-    mGroup = mParent->Group();
-    mCrossOriginPolicy = mParent->CrossOriginPolicy();
-  } else if (mOpener) {
-    mGroup = mOpener->Group();
-    mCrossOriginPolicy = mOpener->CrossOriginPolicy();
-  } else {
-    // To ensure the group has a unique ID, we will use our ID, as the founder
-    // of this BrowsingContextGroup.
-    mGroup = new BrowsingContextGroup();
-  }
+  MOZ_RELEASE_ASSERT(!mParent || mParent->Group() == mGroup);
+  MOZ_RELEASE_ASSERT(mBrowsingContextId != 0);
+  MOZ_RELEASE_ASSERT(mGroup);
 }
 
 void BrowsingContext::SetDocShell(nsIDocShell* aDocShell) {
   // XXX(nika): We should communicate that we are now an active BrowsingContext
   // process to the parent & do other validation here.
   MOZ_RELEASE_ASSERT(nsDocShell::Cast(aDocShell)->GetBrowsingContext() == this);
   mDocShell = aDocShell;
   mIsInProcess = true;
@@ -200,20 +209,18 @@ void BrowsingContext::Attach(bool aFromI
 
   auto* children = mParent ? &mParent->mChildren : &mGroup->Toplevels();
   MOZ_DIAGNOSTIC_ASSERT(!children->Contains(this));
 
   children->AppendElement(this);
 
   // Send attach to our parent if we need to.
   if (!aFromIPC && XRE_IsContentProcess()) {
-    auto cc = ContentChild::GetSingleton();
-    MOZ_DIAGNOSTIC_ASSERT(cc);
-    cc->SendAttachBrowsingContext(
-        mParent, mOpener, BrowsingContextId(mBrowsingContextId), Name());
+    ContentChild::GetSingleton()->SendAttachBrowsingContext(
+        GetIPCInitializer());
   }
 }
 
 void BrowsingContext::Detach(bool aFromIPC) {
   MOZ_LOG(GetLog(), LogLevel::Debug,
           ("%s: Detaching 0x%08" PRIx64 " from 0x%08" PRIx64,
            XRE_IsParentProcess() ? "Parent" : "Child", Id(),
            mParent ? mParent->Id() : 0));
@@ -691,16 +698,34 @@ void BrowsingContext::Transaction::Apply
     aBrowsingContext->WillSet##name(*m##name, aSource); \
     aBrowsingContext->m##name = std::move(*m##name);    \
     aBrowsingContext->DidSet##name(aSource);            \
     m##name.reset();                                    \
   }
 #include "mozilla/dom/BrowsingContextFieldList.h"
 }
 
+already_AddRefed<BrowsingContext> BrowsingContext::IPCInitializer::GetParent() {
+  RefPtr<BrowsingContext> parent;
+  if (mParentId != 0) {
+    parent = BrowsingContext::Get(mParentId);
+    MOZ_RELEASE_ASSERT(parent);
+  }
+  return parent.forget();
+}
+
+already_AddRefed<BrowsingContext> BrowsingContext::IPCInitializer::GetOpener() {
+  RefPtr<BrowsingContext> opener;
+  if (mOpenerId != 0) {
+    opener = BrowsingContext::Get(mOpenerId);
+    MOZ_RELEASE_ASSERT(opener);
+  }
+  return opener.forget();
+}
+
 void BrowsingContext::LocationProxy::SetHref(const nsAString& aHref,
                                              nsIPrincipal& aSubjectPrincipal,
                                              ErrorResult& aError) {
   nsPIDOMWindowOuter* win = GetBrowsingContext()->GetDOMWindow();
   if (!win || !win->GetLocation()) {
     aError.Throw(NS_ERROR_FAILURE);
     return;
   }
@@ -775,10 +800,47 @@ bool IPDLParamTraits<dom::BrowsingContex
   if (!ReadIPDLParam(aMessage, aIterator, aActor, &aTransaction->m##name)) { \
     return false;                                                            \
   }
 #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.
+  // NOTE: mOpenerId is synced separately due to ordering issues.
+  WriteIPDLParam(aMessage, aActor, aInit.mId);
+  WriteIPDLParam(aMessage, aActor, aInit.mParentId);
+  WriteIPDLParam(aMessage, aActor, aInit.mOpenerId);
+
+  // Write other synchronized fields.
+#define MOZ_BC_FIELD_SKIP_OPENER
+#define MOZ_BC_FIELD(name, ...) WriteIPDLParam(aMessage, aActor, aInit.m##name);
+#include "mozilla/dom/BrowsingContextFieldList.h"
+}
+
+bool IPDLParamTraits<dom::BrowsingContext::IPCInitializer>::Read(
+    const IPC::Message* aMessage, PickleIterator* aIterator, IProtocol* aActor,
+    dom::BrowsingContext::IPCInitializer* aInit) {
+  // Read actor ID parameters.
+  // NOTE: mOpenerId is synced separately due to ordering issues.
+  if (!ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mId) ||
+      !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mParentId) ||
+      !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mOpenerId)) {
+    return false;
+  }
+
+  // Read other synchronized fields.
+#define MOZ_BC_FIELD_SKIP_OPENER
+#define MOZ_BC_FIELD(name, ...)                                       \
+  if (!ReadIPDLParam(aMessage, aIterator, aActor, &aInit->m##name)) { \
+    return false;                                                     \
+  }
+#include "mozilla/dom/BrowsingContextFieldList.h"
+
+  return true;
+}
+
 }  // namespace ipc
 }  // namespace mozilla
--- a/docshell/base/BrowsingContext.h
+++ b/docshell/base/BrowsingContext.h
@@ -105,21 +105,16 @@ class BrowsingContext : public nsWrapper
   }
 
   // Create a brand-new BrowsingContext object.
   static already_AddRefed<BrowsingContext> Create(BrowsingContext* aParent,
                                                   BrowsingContext* aOpener,
                                                   const nsAString& aName,
                                                   Type aType);
 
-  // Create a BrowsingContext object from over IPC.
-  static already_AddRefed<BrowsingContext> CreateFromIPC(
-      BrowsingContext* aParent, BrowsingContext* aOpener,
-      const nsAString& aName, uint64_t aId, ContentParent* aOriginProcess);
-
   // Cast this object to a canonical browsing context, and return it.
   CanonicalBrowsingContext* Canonical();
 
   // 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 IsInProcess() const { return mIsInProcess; }
 
@@ -209,16 +204,25 @@ class BrowsingContext : public nsWrapper
 
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(BrowsingContext)
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(BrowsingContext)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(BrowsingContext)
 
   using Children = nsTArray<RefPtr<BrowsingContext>>;
   const Children& GetChildren() { return mChildren; }
 
+  // Perform a pre-order walk of this BrowsingContext subtree.
+  template <typename Func>
+  void PreOrderWalk(Func&& aCallback) {
+    aCallback(this);
+    for (auto& child : GetChildren()) {
+      child->PreOrderWalk(aCallback);
+    }
+  }
+
   // Window APIs that are cross-origin-accessible (from the HTML spec).
   BrowsingContext* Window() { return Self(); }
   BrowsingContext* Self() { return this; }
   void Location(JSContext* aCx, JS::MutableHandle<JSObject*> aLocation,
                 ErrorResult& aError);
   void Close(CallerType aCallerType, ErrorResult& aError);
   bool GetClosed(ErrorResult&) { return mClosed; }
   void Focus(ErrorResult& aError);
@@ -272,21 +276,68 @@ class BrowsingContext : public nsWrapper
     Transaction txn;                                    \
     txn.m##name.emplace(std::forward<Args>(aValue)...); \
     txn.Commit(this);                                   \
   }                                                     \
                                                         \
   type const& Get##name() const { return m##name; }
 #include "mozilla/dom/BrowsingContextFieldList.h"
 
+  /**
+   * Information required to initialize a BrowsingContext in another process.
+   * This object may be serialized over IPC.
+   */
+  struct IPCInitializer {
+    uint64_t mId;
+
+    // IDs are used for Parent and Opener to allow for this object to be
+    // deserialized before other BrowsingContext in the BrowsingContextGroup
+    // have been initialized.
+    uint64_t mParentId;
+    uint64_t mOpenerId;
+    already_AddRefed<BrowsingContext> GetParent();
+    already_AddRefed<BrowsingContext> GetOpener();
+
+    // Include each field, skipping mOpener, as we want to handle it
+    // separately.
+#define MOZ_BC_FIELD_SKIP_OPENER
+#define MOZ_BC_FIELD(name, type) type m##name;
+#include "mozilla/dom/BrowsingContextFieldList.h"
+  };
+
+  // Create an IPCInitializer object for this BrowsingContext.
+  IPCInitializer GetIPCInitializer() {
+    IPCInitializer init;
+    init.mId = Id();
+    init.mParentId = mParent ? mParent->Id() : 0;
+    init.mOpenerId = mOpener ? mOpener->Id() : 0;
+
+#define MOZ_BC_FIELD_SKIP_OPENER
+#define MOZ_BC_FIELD(name, type) init.m##name = m##name;
+#include "mozilla/dom/BrowsingContextFieldList.h"
+    return init;
+  }
+
+  // Create a BrowsingContext object from over IPC. This method does not
+  // initialize the Opener field, which will need to be set after using the
+  // InitFromIPC method.
+  static already_AddRefed<BrowsingContext> CreateFromIPC(
+      IPCInitializer&& aInitializer, BrowsingContextGroup* aGroup,
+      ContentParent* aOriginProcess);
+
+  // Initialize the Opener property which was not set during CreateFromIPC.
+  void InitFromIPC(uint64_t aOpenerId) {
+    mOpener = BrowsingContext::Get(aOpenerId);
+    MOZ_RELEASE_ASSERT(mOpener || aOpenerId == 0);
+  }
+
  protected:
   virtual ~BrowsingContext();
-  BrowsingContext(BrowsingContext* aParent, BrowsingContext* aOpener,
-                  const nsAString& aName, uint64_t aBrowsingContextId,
-                  Type aType);
+  BrowsingContext(BrowsingContext* aParent, BrowsingContextGroup* aGroup,
+                  uint64_t aBrowsingContextId, Type aType);
 
  private:
   // Find the special browsing context if aName is '_self', '_parent',
   // '_top', but not '_blank'. The latter is handled in FindWithName
   BrowsingContext* FindWithSpecialName(const nsAString& aName);
 
   // Find a browsing context in the subtree rooted at 'this' Doesn't
   // consider the special names, '_self', '_parent', '_top', or
@@ -386,16 +437,17 @@ class BrowsingContext : public nsWrapper
  * lives in this process, and a same-process WindowProxy should be used (see
  * nsGlobalWindowOuter). This should only be called by bindings code, ToJSValue
  * is the right API to get a WindowProxy for a BrowsingContext.
  */
 extern bool GetRemoteOuterWindowProxy(JSContext* aCx, BrowsingContext* aContext,
                                       JS::MutableHandle<JSObject*> aRetVal);
 
 typedef BrowsingContext::Transaction BrowsingContextTransaction;
+typedef BrowsingContext::IPCInitializer BrowsingContextInitializer;
 
 }  // namespace dom
 
 // Allow sending BrowsingContext objects over IPC.
 namespace ipc {
 template <>
 struct IPDLParamTraits<dom::BrowsingContext> {
   static void Write(IPC::Message* aMsg, IProtocol* aActor,
@@ -409,12 +461,22 @@ struct IPDLParamTraits<dom::BrowsingCont
   static void Write(IPC::Message* aMessage, IProtocol* aActor,
                     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::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);
+};
+
 }  // namespace ipc
 }  // namespace mozilla
 
 #endif  // !defined(mozilla_dom_BrowsingContext_h)
--- a/docshell/base/BrowsingContextGroup.cpp
+++ b/docshell/base/BrowsingContextGroup.cpp
@@ -38,31 +38,42 @@ void BrowsingContextGroup::Unsubscribe(C
 }
 
 void BrowsingContextGroup::EnsureSubscribed(ContentParent* aProcess) {
   MOZ_DIAGNOSTIC_ASSERT(aProcess);
   if (mSubscribers.Contains(aProcess)) {
     return;
   }
 
-  MOZ_RELEASE_ASSERT(
-      mContexts.Count() == 1,
-      "EnsureSubscribed doesn't work on non-fresh BrowsingContextGroups yet!");
-
   // Subscribe to the BrowsingContext, and send down initial state!
   Subscribe(aProcess);
 
-  // XXX(nika): We can't send down existing BrowsingContextGroups reliably yet
-  // due to ordering issues! (Bug ?)
+  // Iterate over each of our browsing contexts, locating those which are not in
+  // their parent's children list. We can then use those as starting points to
+  // get a pre-order walk of each tree.
+  nsTArray<BrowsingContext::IPCInitializer> inits(mContexts.Count());
   for (auto iter = mContexts.Iter(); !iter.Done(); iter.Next()) {
-    RefPtr<BrowsingContext> bc = iter.Get()->GetKey();
-    Unused << aProcess->SendAttachBrowsingContext(
-        bc->GetParent(), bc->GetOpener(), BrowsingContextId(bc->Id()),
-        bc->Name());
+    auto* context = iter.Get()->GetKey();
+
+    // If we have a parent, and are in our parent's `Children` list, skip
+    // ourselves as we'll be found in the pre-order traversal of our parent.
+    if (context->GetParent() &&
+        context->GetParent()->GetChildren().IndexOf(context) !=
+            BrowsingContext::Children::NoIndex) {
+      continue;
+    }
+
+    // Add all elements to the list in pre-order.
+    context->PreOrderWalk([&](BrowsingContext* aContext) {
+      inits.AppendElement(aContext->GetIPCInitializer());
+    });
   }
+
+  // Send all of our contexts to the target content process.
+  Unused << aProcess->SendRegisterBrowsingContextGroup(inits);
 }
 
 BrowsingContextGroup::~BrowsingContextGroup() {
   for (auto iter = mSubscribers.Iter(); !iter.Done(); iter.Next()) {
     nsRefPtrHashKey<ContentParent>* entry = iter.Get();
     entry->GetKey()->OnBrowsingContextGroupUnsubscribe(this);
   }
 }
--- a/docshell/base/BrowsingContextGroup.h
+++ b/docshell/base/BrowsingContextGroup.h
@@ -56,16 +56,38 @@ class BrowsingContextGroup final : publi
   }
 
   nsISupports* GetParentObject() const;
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
   BrowsingContextGroup() = default;
 
+  static already_AddRefed<BrowsingContextGroup> Select(
+      BrowsingContext* aParent, BrowsingContext* aOpener) {
+    if (aParent) {
+      return do_AddRef(aParent->Group());
+    }
+    if (aOpener) {
+      return do_AddRef(aOpener->Group());
+    }
+    return MakeAndAddRef<BrowsingContextGroup>();
+  }
+
+  static already_AddRefed<BrowsingContextGroup> Select(uint64_t aParentId,
+                                                       uint64_t aOpenerId) {
+    RefPtr<BrowsingContext> parent = BrowsingContext::Get(aParentId);
+    MOZ_RELEASE_ASSERT(parent || aParentId == 0);
+
+    RefPtr<BrowsingContext> opener = BrowsingContext::Get(aOpenerId);
+    MOZ_RELEASE_ASSERT(opener || aOpenerId == 0);
+
+    return Select(parent, opener);
+  }
+
  private:
   friend class CanonicalBrowsingContext;
 
   ~BrowsingContextGroup();
 
   // A BrowsingContextGroup contains a series of BrowsingContext objects. They
   // are addressed using a hashtable to avoid linear lookup when adding or
   // removing elements from the set.
--- a/docshell/base/CanonicalBrowsingContext.cpp
+++ b/docshell/base/CanonicalBrowsingContext.cpp
@@ -14,22 +14,21 @@ namespace mozilla {
 namespace dom {
 
 extern mozilla::LazyLogModule gUserInteractionPRLog;
 
 #define USER_ACTIVATION_LOG(msg, ...) \
   MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
 
 CanonicalBrowsingContext::CanonicalBrowsingContext(BrowsingContext* aParent,
-                                                   BrowsingContext* aOpener,
-                                                   const nsAString& aName,
+                                                   BrowsingContextGroup* aGroup,
                                                    uint64_t aBrowsingContextId,
                                                    uint64_t aProcessId,
                                                    BrowsingContext::Type aType)
-    : BrowsingContext(aParent, aOpener, aName, aBrowsingContextId, aType),
+    : BrowsingContext(aParent, aGroup, aBrowsingContextId, aType),
       mProcessId(aProcessId) {
   // You are only ever allowed to create CanonicalBrowsingContexts in the
   // parent process.
   MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
 }
 
 /* static */
 already_AddRefed<CanonicalBrowsingContext> CanonicalBrowsingContext::Get(
--- a/docshell/base/CanonicalBrowsingContext.h
+++ b/docshell/base/CanonicalBrowsingContext.h
@@ -65,19 +65,20 @@ class CanonicalBrowsingContext final : p
   bool ValidateTransaction(const Transaction& aTransaction,
                            ContentParent* aSource);
 
  protected:
   void Traverse(nsCycleCollectionTraversalCallback& cb);
   void Unlink();
 
   using Type = BrowsingContext::Type;
-  CanonicalBrowsingContext(BrowsingContext* aParent, BrowsingContext* aOpener,
-                           const nsAString& aName, uint64_t aBrowsingContextId,
-                           uint64_t aProcessId, Type aType = Type::Chrome);
+  CanonicalBrowsingContext(BrowsingContext* aParent,
+                           BrowsingContextGroup* aGroup,
+                           uint64_t aBrowsingContextId, uint64_t aProcessId,
+                           Type aType);
 
  private:
   friend class BrowsingContext;
 
   // XXX(farre): Store a ContentParent pointer here rather than mProcessId?
   // Indicates which process owns the docshell.
   uint64_t mProcessId;
 
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -1889,17 +1889,17 @@ bool nsFrameLoader::ShouldUseRemoteProce
                                     nsGkAtoms::_true, eCaseMatters);
 }
 
 static already_AddRefed<BrowsingContext> CreateBrowsingContext(
     BrowsingContext* aParentContext, BrowsingContext* aOpenerContext,
     const nsAString& aName, bool aIsContent) {
   // If we're content but our parent isn't, we're going to want to start a new
   // browsing context tree.
-  if (aIsContent && !aParentContext->IsContent()) {
+  if (aIsContent && aParentContext && !aParentContext->IsContent()) {
     aParentContext = nullptr;
   }
 
   BrowsingContext::Type type = aIsContent ? BrowsingContext::Type::Content
                                           : BrowsingContext::Type::Chrome;
 
   return BrowsingContext::Create(aParentContext, aOpenerContext, aName, type);
 }
@@ -2580,20 +2580,24 @@ bool nsFrameLoader::TryRemoteBrowser() {
   nsCOMPtr<Element> ownerElement = mOwnerContent;
 
   // If we're in a content process, create a BrowserBridgeChild actor.
   if (XRE_IsContentProcess()) {
     // Determine the frame name for the new browsing context.
     nsAutoString frameName;
     GetFrameName(mOwnerContent, frameName);
 
+    RefPtr<BrowsingContext> parentBC;
+    parentDocShell->GetBrowsingContext(getter_AddRefs(parentBC));
+    MOZ_ASSERT(parentBC, "docShell must have BrowsingContext");
+
     // XXX(nika): due to limitations with Browsing Context Groups and multiple
     // processes, we can't link up aParent yet! (Bug 1532661)
     RefPtr<BrowsingContext> browsingContext =
-        CreateBrowsingContext(nullptr, nullptr, frameName, true);
+        CreateBrowsingContext(parentBC, nullptr, frameName, true);
 
     mBrowserBridgeChild = BrowserBridgeChild::Create(
         this, context, NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), browsingContext);
     return !!mBrowserBridgeChild;
   }
 
   mRemoteBrowser =
       ContentParent::CreateBrowser(context, ownerElement, openerContentParent,
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -23,16 +23,18 @@
 #include "mozilla/ProcessHangMonitorIPC.h"
 #include "mozilla/RemoteDecoderManagerChild.h"
 #include "mozilla/Unused.h"
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/TelemetryIPC.h"
 #include "mozilla/VideoDecoderManagerChild.h"
 #include "mozilla/devtools/HeapSnapshotTempFileHelperChild.h"
 #include "mozilla/docshell/OfflineCacheUpdateChild.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
 #include "mozilla/dom/ClientManager.h"
 #include "mozilla/dom/ClientOpenWindowOpActors.h"
 #include "mozilla/dom/ChildProcessMessageManager.h"
 #include "mozilla/dom/ContentProcessMessageManager.h"
 #include "mozilla/dom/DOMPrefs.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/DocGroup.h"
@@ -3682,24 +3684,26 @@ PContentChild::Result ContentChild::OnMe
 
     LSObject::OnSyncMessageHandled();
   }
 
   return result;
 }
 
 mozilla::ipc::IPCResult ContentChild::RecvAttachBrowsingContext(
-    BrowsingContext* aParent, BrowsingContext* aOpener,
-    BrowsingContextId aChildId, const nsString& aName) {
-  RefPtr<BrowsingContext> child = BrowsingContext::Get(aChildId);
+    BrowsingContext::IPCInitializer&& aInit) {
+  RefPtr<BrowsingContext> child = BrowsingContext::Get(aInit.mId);
   MOZ_RELEASE_ASSERT(!child || child->IsCached());
 
   if (!child) {
-    child = BrowsingContext::CreateFromIPC(aParent, aOpener, aName,
-                                           (uint64_t)aChildId, nullptr);
+    // Determine the BrowsingContextGroup from our parent or opener fields.
+    RefPtr<BrowsingContextGroup> group =
+        BrowsingContextGroup::Select(aInit.mParentId, aInit.mOpenerId);
+    child = BrowsingContext::CreateFromIPC(std::move(aInit), group, nullptr);
+    child->InitFromIPC(aInit.mOpenerId);
   }
 
   child->Attach(/* aFromIPC */ true);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentChild::RecvDetachBrowsingContext(
@@ -3710,16 +3714,57 @@ mozilla::ipc::IPCResult ContentChild::Re
     aContext->CacheChildren(/* aFromIPC */ true);
   } else {
     aContext->Detach(/* aFromIPC */ true);
   }
 
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult ContentChild::RecvRegisterBrowsingContextGroup(
+    nsTArray<BrowsingContext::IPCInitializer>&& aInits) {
+  RefPtr<BrowsingContextGroup> group = new BrowsingContextGroup();
+
+  // Hold a grip on each created BrowsingContext to ensure they don't die too
+  // early.
+  AutoTArray<RefPtr<BrowsingContext>, 8> grip;
+  grip.SetCapacity(aInits.Length());
+
+  // Each of the initializers in aInits is sorted in pre-order, so our parent
+  // should always be available before the element itself.
+  for (auto& init : aInits) {
+#ifdef DEBUG
+    RefPtr<BrowsingContext> existing = BrowsingContext::Get(init.mId);
+    MOZ_ASSERT(!existing, "BrowsingContext must not exist yet!");
+
+    RefPtr<BrowsingContext> parent = init.GetParent();
+    MOZ_ASSERT_IF(parent, parent->Group() == group);
+#endif
+
+    // Create a BrowsingContext and add it to our grip list.
+    auto* ctxt = grip.AppendElement();
+    *ctxt = BrowsingContext::CreateFromIPC(std::move(init), group, nullptr);
+
+    // FIXME: We should deal with cached & detached contexts as well.
+    (*ctxt)->Attach(/* aFromIPC */ true);
+  }
+
+  // Perform a second pass to initialize the `opener` fields of each
+  // BrowsingContext.
+  // Our `mId` and `mOpenerId` fields won't have been moved by the above loop,
+  // so they're OK to access here.
+  MOZ_ASSERT(grip.Length() == aInits.Length());
+  for (uint32_t i = 0; i < grip.Length(); ++i) {
+    MOZ_ASSERT(grip[i]->Id() == aInits[i].mId);
+    grip[i]->InitFromIPC(aInits[i].mOpenerId);
+  }
+
+  return IPC_OK();
+}
+
 mozilla::ipc::IPCResult ContentChild::RecvWindowClose(BrowsingContext* aContext,
                                                       bool aTrustedCaller) {
   if (!aContext) {
     MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
             ("ChildIPC: Trying to send a message to dead or detached context"));
     return IPC_OK();
   }
 
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -709,22 +709,24 @@ class ContentChild final : public PConte
       const Message& aMsg) override;
 
   virtual already_AddRefed<nsIEventTarget> GetSpecificMessageEventTarget(
       const Message& aMsg) override;
 
   virtual void OnChannelReceivedMessage(const Message& aMsg) override;
 
   mozilla::ipc::IPCResult RecvAttachBrowsingContext(
-      BrowsingContext* aParentContext, BrowsingContext* aOpener,
-      BrowsingContextId aContextId, const nsString& aName);
+      BrowsingContext::IPCInitializer&& aInit);
 
   mozilla::ipc::IPCResult RecvDetachBrowsingContext(BrowsingContext* aContext,
                                                     bool aMoveToBFCache);
 
+  mozilla::ipc::IPCResult RecvRegisterBrowsingContextGroup(
+      nsTArray<BrowsingContext::IPCInitializer>&& aInits);
+
   mozilla::ipc::IPCResult RecvWindowClose(BrowsingContext* aContext,
                                           bool aTrustedCaller);
   mozilla::ipc::IPCResult RecvWindowFocus(BrowsingContext* aContext);
   mozilla::ipc::IPCResult RecvWindowBlur(BrowsingContext* aContext);
   mozilla::ipc::IPCResult RecvWindowPostMessage(
       BrowsingContext* aContext, const ClonedMessageData& aMessage,
       const PostMessageData& aData);
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5690,55 +5690,62 @@ ContentParent::RecvFirstPartyStorageAcce
 
 mozilla::ipc::IPCResult ContentParent::RecvStoreUserInteractionAsPermission(
     const Principal& aPrincipal) {
   AntiTrackingCommon::StoreUserInteractionFor(aPrincipal);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvAttachBrowsingContext(
-    BrowsingContext* aParent, BrowsingContext* aOpener,
-    BrowsingContextId aChildId, const nsString& aName) {
-  if (aParent && !aParent->Canonical()->IsOwnedByProcess(ChildID())) {
+    BrowsingContext::IPCInitializer&& aInit) {
+  RefPtr<CanonicalBrowsingContext> parent;
+  if (aInit.mParentId != 0) {
+    parent = CanonicalBrowsingContext::Get(aInit.mParentId);
+    MOZ_RELEASE_ASSERT(parent, "Parent doesn't exist in parent process");
+  }
+
+  if (parent && !parent->IsOwnedByProcess(ChildID())) {
     // Where trying attach a child BrowsingContext to a parent
     // BrowsingContext in another process. This is illegal since the
     // only thing that could create that child BrowsingContext is a
     // parent docshell in the same process as that BrowsingContext.
 
     // TODO(farre): We're doing nothing now, but is that exactly what
     // we want? Maybe we want to crash the child currently calling
     // SendAttachBrowsingContext and/or the child that originally
     // called SendAttachBrowsingContext or possibly all children that
     // has a BrowsingContext connected to the child that currently
     // called SendAttachBrowsingContext? [Bug 1471598]
     MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Warning,
             ("ParentIPC: Trying to attach to out of process parent context "
              "0x%08" PRIx64,
-             aParent->Id()));
+             aInit.mParentId));
     return IPC_OK();
   }
 
-  RefPtr<BrowsingContext> child = BrowsingContext::Get(aChildId);
+  RefPtr<BrowsingContext> child = BrowsingContext::Get(aInit.mId);
   if (child && !child->IsCached()) {
     // This is highly suspicious. BrowsingContexts should only be
     // attached at most once, but finding one indicates that someone
     // is doing something they shouldn't.
 
     // TODO(farre): To crash or not to crash. Same reasoning as in
     // above TODO. [Bug 1471598]
     MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Warning,
             ("ParentIPC: Trying to attach already attached 0x%08" PRIx64
              " to 0x%08" PRIx64,
-             child->Id(), aParent ? aParent->Id() : 0));
+             aInit.mId, aInit.mParentId));
     return IPC_OK();
   }
 
   if (!child) {
-    child = BrowsingContext::CreateFromIPC(aParent, aOpener, aName,
-                                           (uint64_t)aChildId, this);
+    RefPtr<BrowsingContextGroup> group =
+      BrowsingContextGroup::Select(aInit.mParentId, aInit.mOpenerId);
+    child = BrowsingContext::CreateFromIPC(std::move(aInit), group, this);
+    child->InitFromIPC(aInit.mOpenerId);
   }
 
   child->Attach(/* aFromIPC */ true);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvDetachBrowsingContext(
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -605,18 +605,17 @@ class ContentParent final : public PCont
 
   // Control the priority of the IPC messages for input events.
   void SetInputPriorityEventEnabled(bool aEnabled);
   bool IsInputPriorityEventEnabled() { return mIsInputPriorityEventEnabled; }
 
   static bool IsInputEventQueueSupported();
 
   mozilla::ipc::IPCResult RecvAttachBrowsingContext(
-      BrowsingContext* aParentContext, BrowsingContext* aOpener,
-      BrowsingContextId aContextId, const nsString& aName);
+      BrowsingContext::IPCInitializer&& aInit);
 
   mozilla::ipc::IPCResult RecvDetachBrowsingContext(BrowsingContext* aContext,
                                                     bool aMoveToBFCache);
 
   mozilla::ipc::IPCResult RecvWindowClose(BrowsingContext* aContext,
                                           bool aTrustedCaller);
   mozilla::ipc::IPCResult RecvWindowFocus(BrowsingContext* aContext);
   mozilla::ipc::IPCResult RecvWindowBlur(BrowsingContext* aContext);
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -94,16 +94,17 @@ using mozilla::Telemetry::KeyedScalarAct
 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::BrowsingContextId from "mozilla/dom/ipc/IdType.h";
 using mozilla::dom::BrowsingContextTransaction from "mozilla/dom/BrowsingContext.h";
+using mozilla::dom::BrowsingContextInitializer from "mozilla/dom/BrowsingContext.h";
 
 union ChromeRegistryItem
 {
     ChromePackage;
     OverrideMapping;
     SubstitutionMapping;
 };
 
@@ -771,16 +772,20 @@ child:
     async CrossProcessRedirect(uint32_t aRegistrarId,
                                nsIURI aURI,
                                uint32_t aNewLoadFlags,
                                LoadInfoArgs? aLoadInfo,
                                uint64_t aChannelId,
                                nsIURI aOriginalURI,
                                uint64_t aIdentifier);
 
+    // Begin subscribing to a new BrowsingContextGroup, sending down the current
+    // value for every individual BrowsingContext.
+    async RegisterBrowsingContextGroup(BrowsingContextInitializer[] aInits);
+
 parent:
     async InitBackground(Endpoint<PBackgroundParent> aEndpoint);
 
     sync OpenRecordReplayChannel(uint32_t channelId)
         returns (FileDescriptor connection);
     async CreateReplayingProcess(uint32_t channelId);
 
     async CreateGMPService();
@@ -1247,20 +1252,17 @@ both:
     /**
      * Sync the BrowsingContext with id 'aContextId' and name 'aName' to the
      * parent, and attach it to the BrowsingContext 'aParentContext'. If
      * 'aParentContext' is 'nullptr' the BrowsingContext is a root in the
      * BrowsingContext tree. AttachBrowsingContext must only be called at most
      * once for any child BrowsingContext, and only for BrowsingContexts where
      * the parent and the child context contains their nsDocShell.
      */
-    async AttachBrowsingContext(BrowsingContext aParentContext,
-                                BrowsingContext aOpener,
-                                BrowsingContextId aContextId,
-                                nsString aName);
+    async AttachBrowsingContext(BrowsingContextInitializer aInit);
 
     /**
      * Remove the synced BrowsingContext 'aContext' from the parent.
      * DetachBrowsingContext is only needed to be called once for any
      * BrowsingContext, since detaching a node in the BrowsingContext detaches
      * the entire sub-tree rooted at that node. Calling DetachBrowsingContext
      * with an already detached BrowsingContext effectively does nothing. Note
      * that it is not an error to call DetachBrowsingContext on a
--- a/dom/ipc/tests/test_force_oop_iframe.html
+++ b/dom/ipc/tests/test_force_oop_iframe.html
@@ -8,17 +8,18 @@
 </head>
 <body>
 
 <script type="application/javascript">
 "use strict";
 /* eslint-env mozilla/frame-script */
 
 add_task(async function() {
-  await SpecialPowers.pushPrefEnv({"set": [["fission.oopif.attribute", true]]});
+  await SpecialPowers.pushPrefEnv({"set": [["fission.oopif.attribute", true],
+                                           ["dom.ipc.processCount", 10000]]});
 
   // This iframe should be loaded out of process. Unfortunately as of the time
   // of this test's creation, many different events which we could use to detect
   // this load have not been implemented yet.
   let contentCreated = ChromeTask.spawn(null, async function() {
     let wgp = await new Promise(resolve => {
       function observer(parent) {
         info("WGP with origin: " + parent.documentPrincipal.origin);
@@ -43,13 +44,22 @@ add_task(async function() {
   document.body.appendChild(iframe);
 
   // Check that this isn't loaded in-process, or using a nested tabParent object.
   let frameLoader = SpecialPowers.wrap(iframe).frameLoader;
   is(frameLoader.docShell, null);
   is(frameLoader.tabParent, null);
 
   await contentCreated;
+
+  ok(frameLoader.browsingContext, "Has BrowsingContext");
+  ok(frameLoader.browsingContext.parent, "BrowsingContext has parent");
+
+  let wgc = SpecialPowers.wrap(window).getWindowGlobalChild();
+  ok(wgc, "we have a WindowGlobalChild");
+  is(SpecialPowers.unwrap(wgc.browsingContext),
+     SpecialPowers.unwrap(frameLoader.browsingContext.parent),
+     "BrowsingContext matches");
 });
 
 </script>
 </body>
 </html>